react组件汇总
# React 组件
# 类组件和函数组件之间的区别是啥?
- 类组件可以使用其他特性,如状态
state
和生命周期钩子。 - 当组件只是接收
props
渲染到页面时,就是无状态组件,就属于函数组件,也被称为哑组件或展示组件。
函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。
区别 | 函数组件 | 类组件 |
---|---|---|
是否有 this | 没有 | 有 |
是否有生命周期 | 没有 | 有 |
是否有状态 state | 没有 | 有 |
在 React 16.8版本(引入钩子)之前,使用基于类的组件来创建需要维护内部状态或利用生命周期方法的组件(即componentDidMount
和shouldComponentUpdate
)。基于类的组件是 ES6 类,它扩展了 React 的 Component 类,并且至少实现了render()
方法。
类组件:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
2
3
4
5
函数组件是无状态的(同样,小于 React 16.8版本),并返回要呈现的输出。它们渲染 UI 的首选只依赖于属性,因为它们比基于类的组件更简单、更具性能。
函数组件:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
2
3
注意:在 React 16.8版本中引入钩子意味着这些区别不再适用;
# 你理解“在React中,一切都是组件”这句话。
组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。
# 解释 React 中 render() 的目的。
每个React组件强制要求必须有一个 render()。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 <form>
、<group>
、<div>
等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。
# 如何将两个或多个组件嵌入到一个组件中?
可以通过以下方式将组件嵌入到一个组件中:
class MyComponent extends React.Component{
render(){
return(
<div>
<h1>Hello</h1>
<Header/>
</div>
);
}
}
class Header extends React.Component{
render(){
return
<h1>Header Component</h1>
};
}
ReactDOM.render(
<MyComponent/>, document.getElementById('content')
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 无状态组件(函数组件)
当一个组件只有一个render()
函数时,我们就可将这个组件定义为无状态组件,无状态组件只有一个函数。
无状态组件的性能比较高,因为它仅是一个函数,而普通组件是一个class
。
- 纯函数
- 输入props,输出JSX
- 没有实例
- 没有生命周期
- 没有state
- 不能扩展其它方法
function List (props) {
const { text } = this.props
return (
<div>{text}</div>
)
}
2
3
4
5
6
# 什么是纯组件?
纯(Pure) 组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能。
# 什么是纯函数?
纯函数是不依赖并且不会在其作用域之外修改变量状态的函数。本质上,纯函数始终在给定相同参数的情况下返回相同结果。
# 什么是控制组件,比如表单?
在 HTML 中,表单元素如 <input>
、<textarea>
和<select>
通常维护自己的状态,并根据用户输入进行更新。当用户提交表单时,来自上述元素的值将随表单一起发送。
而 React 的工作方式则不同。包含表单的组件将跟踪其状态中的输入值,并在每次回调函数(例如onChange
)触发时重新渲染组件,因为状态被更新。以这种方式由 React 控制其值的输入表单元素称为受控组件。
# 你对受控组件和非受控组件了解多少?
受控组件 | 非受控组件 |
---|---|
1. 没有维持自己的状态 | 1. 保持着自己的状态 |
2.数据由父组件控制 | 2.数据由 DOM 控制 |
3. 通过 props 获取当前值,然后通过回调通知更改 | 3. Refs 用于获取其当前值 |
# 什么是受控组件和非受控组件
- 受状态控制的组件,必须要有onChange方法,否则不能使用 受控组件可以赋予默认值(官方推荐使用 受控组件) 实现双向数据绑定
class Input extends Component{
constructor(){
super();
this.state = {val:'100'}
}
handleChange = (e) =>{ //e是事件源
let val = e.target.value;
this.setState({val});
// 下面这种写法会报错,因为 this.setState 传递一个函数时,为异步方法,等异步执行时已经没有 event
this.setState(() => ({
userName = event.target.value
}))
};
render(){
return (<div>
<input type="text" value={this.state.val} onChange={this.handleChange}/>
{this.state.val}
</div>)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 非受控也就意味着我可以不需要设置它的state属性,而通过ref来操作真实的DOM
class Sum extends Component{
constructor(){
super();
this.state = {result:''}
}
//通过ref设置的属性 可以通过this.refs获取到对应的dom元素
handleChange = () =>{
let result = this.refs.a.value + this.b.value;
this.setState({result});
};
render(){
return (
<div onChange={this.handleChange}>
<input type="number" ref="a"/>
{/*x代表的真实的dom,把元素挂载在了当前实例上*/}
<input type="number" ref={(x)=>{
this.b = x;
}}/>
{this.state.result}
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
获取ref的另外一种方式:
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
name: '',
flag: true
}
this.nameInputRef = React.createRef() // 创建 ref
this.fileInputRef = React.createRef() // 创建 ref
}
render () {
return (
<div>
{/* 这里使用 defaultValue 而不是value,使用 ref */}
<input defaultValue={this.state.name} ref={this.nameInputRef} />
<button onClick={this.alertName.bind(this)}>alert value</button>
{/* file 类型的必须用 ref 获取 dom 来获取数据 */}
<input type="file" ref={this.fileInputRef} />
</div>
)
}
alertName () {
const ele = this.nameInputRef.current // 通过 ref 获取 dom 节点
alert(ele.value)
}
}
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
# 受控组件和非受控组件区别是啥?
- 受控组件是 React 控制中的组件,并且是表单数据真实的唯一来源。
- 非受控组件是由 DOM 处理表单数据的地方,而不是在 React 组件中。
尽管非受控组件通常更易于实现,因为只需使用refs
即可从 DOM 中获取值,但通常建议优先选择受控制的组件,而不是非受控制的组件。
这样做的主要原因是受控组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式。
# React中元素与组件的区别
# React 元素(React element)
它是 React 中最小基本单位,我们可以使用 JSX 语法轻松地创建一个 React 元素:
const element = <div className="element">I'm element</div>
React 元素不是真实的 DOM 元素,它仅仅是 js 的普通对象(plain objects),所以也没办法直接调用 DOM 原生的 API。上面的 JSX 转译后的对象大概是这样的:
{
_context: Object,
_owner: null,
key: null,
props: {
className: 'element',
children: 'I'm element'
},
ref: null,
type: "div"
}
2
3
4
5
6
7
8
9
10
11
除了使用 JSX 语法,我们还可以使用 React.createElement() 和 React.cloneElement() 来构建 React 元素。
# React 组件
React 中有三种构建组件的方式。React.createClass()、ES6 class和无状态函数。
1、React.createClass()
var Greeting = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
2
3
4
5
2、ES6 class
class Greeting extends React.Component{
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
};
2
3
4
5
3、无状态函数
无状态函数是使用函数构建的无状态组件,无状态组件传入props和context两个参数,它没有state,除了render(),没有其它生命周期方法。
function Greeting (props) {
return <h1>Hello, {props.name}</h1>;
}
2
3
4、PureComponent
除了为你提供了一个具有浅比较的shouldComponentUpdate方法,PureComponent和Component基本上完全相同。
# 元素与组件的区别
组件是由元素构成的。元素数据结构是普通对象,而组件数据结构是类或纯函数。
# React组件命名推荐的方式是哪个?
通过引用而不是使用来命名组件displayName。
使用displayName命名组件:
export default React.createClass({
displayName: 'TodoApp',
// ...
})
2
3
4
React推荐的方法:
export default class TodoApp extends React.Component {
// ...
}
2
3
# react 实现一个全局的 dialog
import React, { Component } from 'react';
import { is, fromJS } from 'immutable';
import ReactDOM from 'react-dom';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
import './dialog.css';
let defaultState = {
alertStatus:false,
alertTip:"提示",
closeDialog:function(){},
childs:''
}
class Dialog extends Component{
state = {
...defaultState
};
// css动画组件设置为目标组件
FirstChild = props => {
const childrenArray = React.Children.toArray(props.children);
return childrenArray[0] || null;
}
//打开弹窗
open =(options)=>{
options = options || {};
options.alertStatus = true;
var props = options.props || {};
var childs = this.renderChildren(props,options.childrens) || '';
console.log(childs);
this.setState({
...defaultState,
...options,
childs
})
}
//关闭弹窗
close(){
this.state.closeDialog();
this.setState({
...defaultState
})
}
renderChildren(props,childrens) {
//遍历所有子组件
var childs = [];
childrens = childrens || [];
var ps = {
...props, //给子组件绑定props
_close:this.close //给子组件也绑定一个关闭弹窗的事件
};
childrens.forEach((currentItem,index) => {
childs.push(React.createElement(
currentItem,
{
...ps,
key:index
}
));
})
return childs;
}
shouldComponentUpdate(nextProps, nextState){
return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
}
render(){
return (
<ReactCSSTransitionGroup
component={this.FirstChild}
transitionName='hide'
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
<div className="dialog-con" style={this.state.alertStatus? {display:'block'}:{display:'none'}}>
{this.state.childs}
</div>
</ReactCSSTransitionGroup>
);
}
}
let div = document.createElement('div');
let props = {
};
document.body.appendChild(div);
let Box = ReactD
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
83
84
子类:
//子类jsx
import React, { Component } from 'react';
class Child extends Component {
constructor(props){
super(props);
this.state = {date: new Date()};
}
showValue=()=>{
this.props.showValue && this.props.showValue()
}
render() {
return (
<div className="Child">
<div className="content">
Child
<button onClick={this.showValue}>调用父的方法</button>
</div>
</div>
);
}
}
export default Child;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
css:
.dialog-con{
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
}
2
3
4
5
6
7
8
# 如何模块化 React 中的代码?
可以使用 export 和 import 属性来模块化代码。它们有助于在不同的文件中单独编写组件。
//ChildComponent.jsx
export default class ChildComponent extends React.Component {
render() {
return(
<div>
<h1>This is a child component</h1>
</div>
);
}
}
//ParentComponent.jsx
import ChildComponent from './childcomponent.js';
class ParentComponent extends React.Component {
render() {
return(
<div>
<App />
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 如何在 React 中创建表单
React 表单类似于 HTML 表单。**但是在 React 中,状态包含在组件的 state 属性中,并且只能通过 setState()
更新。**因此元素不能直接更新它们的状态,它们的提交是由 JavaScript 函数处理的。此函数可以完全访问用户输入到表单的数据。
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.handleSubmit} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# React 中 key 的重要性是什么?
key 用于识别唯一的 Virtual DOM 元素及其驱动 UI 的相应数据。它们通过回收 DOM 中当前所有的元素来帮助 React 优化渲染。这些 key 必须是唯一的数字或字符串,React 只是重新排序元素而不是重新渲染它们。这可以提高应用程序的性能。
# 为什么列表循环渲染的key最好不要用index
举例说明
变化前数组的值是[1,2,3,4],key就是对应的下标:0,1,2,3
变化后数组的值是[4,3,2,1],key对应的下标也是:0,1,2,3
2
- 那么diff算法在变化前的数组找到key =0的值是1,在变化后数组里找到的key=0的值是4
- 因为子元素不一样就重新删除并更新
- 但是如果加了唯一的key,如下
变化前数组的值是[1,2,3,4],key就是对应的下标:id0,id1,id2,id3
变化后数组的值是[4,3,2,1],key对应的下标也是:id3,id2,id1,id0
2
- 那么diff算法在变化前的数组找到key =id0的值是1,在变化后数组里找到的key=id0的值也是1
- 因为子元素相同,就不删除并更新,只做移动操作,这就提升了性能
# React中props.children和React.Children的区别
在React中,当涉及组件嵌套,在父组件中使用props.children
把所有子组件显示出来。如下:
function ParentComponent(props){
return (
<div>
{props.children}
</div>
)
}
2
3
4
5
6
7
如果想把父组件中的属性传给所有的子组件,需要使用React.Children
方法。
比如,把几个Radio组合起来,合成一个RadioGroup,这就要求所有的Radio具有同样的name属性值。可以这样:把Radio看做子组件,RadioGroup看做父组件,name的属性值在RadioGroup这个父组件中设置。
首先是子组件:
//子组件
function RadioOption(props) {
return (
<label>
<input type="radio" value={props.value} name={props.name} />
{props.label}
</label>
)
}
2
3
4
5
6
7
8
9
然后是父组件,不仅需要把它所有的子组件显示出来,还需要为每个子组件赋上name属性和值:
//父组件用,props是指父组件的props
function renderChildren(props) {
//遍历所有子组件
return React.Children.map(props.children, child => {
if (child.type === RadioOption)
return React.cloneElement(child, {
//把父组件的props.name赋值给每个子组件
name: props.name
})
else
return child
})
}
//父组件
function RadioGroup(props) {
return (
<div>
{renderChildren(props)}
</div>
)
}
function App() {
return (
<RadioGroup name="hello">
<RadioOption label="选项一" value="1" />
<RadioOption label="选项二" value="2" />
<RadioOption label="选项三" value="3" />
</RadioGroup>
)
}
export default App;
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
以上,React.Children.map
让我们对父组件的所有子组件又更灵活的控制。
# React.Children.map和js的map有什么区别?
JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。
# 在React中遍历的方法有哪些?
(1)遍历数组:map && forEach
import React from 'react';
class App extends React.Component {
render() {
let arr = ['a', 'b', 'c', 'd'];
return (
<ul>
{
arr.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
)
}
}
class App extends React.Component {
render() {
let arr = ['a', 'b', 'c', 'd'];
return (
<ul>
{
arr.forEach((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
)
}
}
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
(2)遍历对象:map && for in
class App extends React.Component {
render() {
let obj = {
a: 1,
b: 2,
c: 3
}
return (
<ul>
{
(() => {
let domArr = [];
for(const key in obj) {
if(obj.hasOwnProperty(key)) {
const value = obj[key]
domArr.push(<li key={key}>{value}</li>)
}
}
return domArr;
})()
}
</ul>
)
}
}
// Object.entries() 把对象转换成数组
class App extends React.Component {
render() {
let obj = {
a: 1,
b: 2,
c: 3
}
return (
<ul>
{
Object.entries(obj).map(([key, value], index) => { // item是一个数组,把item解构,写法是[key, value]
return <li key={key}>{value}</li>
})
}
</ul>
)
}
}
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
# State&Props
# React中的状态是什么?它是如何使用的?
状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state()
访问它们。
# 区分有状态和无状态组件
有状态组件 | 无状态组件 |
---|---|
1. 在内存中存储有关组件状态变化的信息 | 1. 计算组件的内部的状态 |
2. 有权改变状态 | 2. 无权改变状态 |
3. 包含过去、现在和未来可能的状态变化情况 | 3. 不包含过去,现在和未来可能发生的状态变化情况 |
4. 接受无状态组件状态变化要求的通知,然后将 props 发送给他们。 | 4.从有状态组件接收 props 并将其视为回调函数。 |
# 为什么不直接更新 state
呢 ?
如果试图直接更新 state
,则不会重新渲染组件。
This.state.message = 'Hello world';// 错误
需要使用setState()
方法来更新 state
。它调度对组件state
对象的更新。当state
改变时,组件通过重新渲染来响应:
This.setState({message: ‘Hello World’});// 正确做法
另外,您还可以谈谈如何不保证状态更新是同步的。如果需要基于另一个状态(或属性)更新组件的状态,请向setState()
传递一个函数,该函数将 state 和 props 作为其两个参数:
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
2
3
# 当调用setState
时,React render
是如何工作的?【要点】
咱们可以将"render
"分为两个步骤:
- 虚拟 DOM 渲染:当
render
方法被调用时,它返回一个新的组件的虚拟 DOM 结构。当调用setState()
时,render
会被再次调用,因为默认情况下shouldComponentUpdate
总是返回true
,所以默认情况下 React 是没有优化的。 - 原生 DOM 渲染:React 只会在虚拟DOM中修改真实DOM节点,而且修改的次数非常少——这是很棒的React特性,它优化了真实DOM的变化,使React变得更快。
# setState到底是异步还是同步?【要点】
先给出答案: 有时表现出异步,有时表现出同步
setState
只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout
中都是同步的。setState
的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)
中的callback
拿到更新后的结果。setState
的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState
多个不同的值,在更新时会对其进行合并批量更新。
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count
}
})
2
3
4
5
# this.setState()相关
import React from 'react'
class App extends React.Component {
constructor (props) {
super(props)
this.state = { count: 0 }
}
componentDidMount () {
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 0
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 0
setTimeout(() => {
this.setState({count: this.state.count + 1 })
console.log(this.state.count) // 2
}, 0)
setTimeout(() => {
this.setState({count: this.state.count + 1 })
console.log(this.state.count) // 3
}, 0)
// setTimeout(function () {
// this.setState({count: this.state.count + 1 })
// console.log(this.state.count) // 报错,this 指向问题
// }, 0)
}
render () {
return <h1>{this.state.count}</h1>
}
}
export default App // 返回高阶函数
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({
count: this.state.count + 1
})
// 正确的写法
const count = this.state.count + 1
this.setState({ count })
2
3
4
5
6
7
正确修改数组值
// 不能使用 push pop splice 等,这样违反了不可变值,会影响 shouldCompententUpdate 判断
this.setState(() => ({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3) // 截取
list4: this.state.list4.filter(item => item > 100) // 筛选
}))
2
3
4
5
6
7
正确修改对象值
this.setState(() => ({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
}))
2
3
4
通常情况下,setState()
为异步更新数据
const count = this.state.count + 1
this.setState({
count: count
}, () => {
// 这个函数没有默认参数
// 类比 Vue 的 $nextTick
console.log(this.state.count) // 打印更新后的值
})
console.log(this.state.count) // 打印更新前的值
2
3
4
5
6
7
8
9
setState()
同步更新数据,在setTimeout()
中setState()
是同步的
setTimeout(() => {
const count = this.state.count + 1
this.setState({ count })
console.log(this.state.count)
})
2
3
4
5
自己定义的 DOM 事件,setState()
是同步的
componentDidMount () {
document.body.addEventListener('click', () => {
const count = this.state.count + 1
this.setState({ count })
console.log(this.state.count)
})
}
2
3
4
5
6
7
【重点】 传入对象,会被合并,结果只执行一次,类似于Object.assgin()
初始值 this.state.count = 0
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
//输出值 this.state.count = 1
2
3
4
5
6
7
8
9
10
11
【重点】 传入函数,不会被合并,因为函数无法合并
初始值 this.state.count = 0
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
//输出值 this.state.count = 3
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# setState 和 batchUpdate(批处理)
# setState
- 有时异步(普通使用),有时同步(setTimeout, DOM事件)
- 有时合并(对象形式),有时不合并(函数形式),比较好理解(类似 Object.assign),函数无法合并
# 核心要点
- setState 主流程
- batchUpdate 机制
- transaction(事务)机制
看this.setState()
是否是异步,看 isBatchingUpdates 的状态,为 true 就是异步,为 false 就是同步;
# 哪些能命中 batchUpdate 机制
- 生命周期(和它调用的函数)
- React 中注册的事件(和它调用的函数)
- React 可以“管理”的入口
# 哪些不能命中 batchUpdate 机制
- setTimeout setInterval等(和它调用的函数)
- 自定义的DOM时间(和它调用的函数)
- React“管不到”的入口
# transaction 事务机制
# 如何避免组件的重新渲染?【要点】
React 中最常见的问题之一是组件不必要地重新渲染。React 提供了两个方法,在这些情况下非常有用:
React.memo()
:这可以防止不必要地重新渲染函数组件PureComponent
:这可以防止不必要地重新渲染类组件
这两种方法都依赖于对传递给组件的props
的浅比较,如果 props
没有改变,那么组件将不会重新渲染。虽然这两种工具都非常有用,但是浅比较会带来额外的性能损失,因此如果使用不当,这两种方法都会对性能产生负面影响。
通过使用 React Profiler,可以在使用这些方法前后对性能进行测量,从而确保通过进行给定的更改来实际改进性能。
# 区分state和 props
条件 | State | Props |
---|---|---|
1. 从父组件中接收初始值 | Yes | Yes |
2. 父组件可以改变值 | No | Yes |
3. 在组件中设置默认值 | Yes | Yes |
4. 在组件的内部变化 | Yes | No |
5. 设置子组件的初始值 | Yes | Yes |
6. 在子组件的内部更改 | No | Yes |
props
和state
是普通的 JS 对象。虽然它们都包含影响渲染输出的信息,但是它们在组件方面的功能是不同的。即
state
是组件自己管理数据,控制自己的状态,可变;props
是外部传入的数据参数,不可变;- 没有
state
的叫做无状态组件,有state
的叫做有状态组件; - 多用
props
,少用state
,也就是多写无状态组件。
# 什么是 Props?
Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。
# 如何更新组件的状态?
可以用 this.setState()
更新组件的状态。
class MyComponent extends React.Component {
constructor() {
super();
this.state = {
name: 'Maxx',
id: '101'
}
}
render()
{
setTimeout(()=>{this.setState({name:'Jaeha', id:'222'})},2000)
return (
<div>
<h1>Hello {this.state.name}</h1>
<h2>Your Id is {this.state.id}</h2>
</div>
);
}
}
ReactDOM.render(
<MyComponent/>, document.getElementById('content')
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 在构造函数调用 super
并将 props
作为参数传入的作用是啥?
在调用 super()
方法之前,子类构造函数无法使用this
引用,ES6 子类也是如此。
将 props
参数传递给 super()
调用的主要原因是在子构造函数中能够通过this.props
来获取传入的 props
。
传递 props
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // { name: 'samy',age: 30 }
}
}
2
3
4
5
6
没传递 props
class MyComponent extends React.Component {
constructor(props) {
super();
console.log(this.props); // undefined
// 但是 Props 参数仍然可用
console.log(props); // Prints { name: 'samy',age: 30 }
}
render() {
// 构造函数外部不受影响
console.log(this.props) // { name: 'samy',age: 30 }
}
}
2
3
4
5
6
7
8
9
10
11
12
上面示例揭示了一点。props
的行为只有在构造函数中是不同的,在构造函数之外也是一样的。
# 根据下面定义的代码,可以找出存在的两个问题吗 ?
请看下面的代码:
答案:
1.在构造函数没有将 props
传递给 super
,它应该包括以下行
constructor(props) {
super(props);
// ...
}
2
3
4
2.事件监听器(通过addEventListener()
分配时)的作用域不正确,因为 ES6 不提供自动绑定。因此,开发人员可以在构造函数中重新分配clickHandler
来包含正确的绑定:
constructor(props) {
super(props);
this.clickHandler = this.clickHandler.bind(this);
// ...
}
2
3
4
5
# 这段代码有什么问题吗?
这段代码有什么问题:
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count
}
})
2
3
4
5
没有什么问题。这种方式很少被使用,咱们可以将一个函数传递给setState
,该函数接收上一个 state
的值和当前的props
,并返回一个新的状态,如果咱们需要根据以前的状态重新设置状态,推荐使用这种方式。
# 这三个点(...)在 React 干嘛用的?
...
在React(使用JSX)代码中做什么?它叫什么?
<Modal {...this.props} title='Modal heading' animation={false}/>
这个叫扩展操作符号或者展开操作符,例如,如果this.props
包含a:1
和b:2
,则
<Modal {...this.props} title='Modal heading' animation={false}>
等价于下面内容:
<Modal a={this.props.a} b={this.props.b} title='Modal heading' animation={false}>
扩展符号不仅适用于该用例,而且对于创建具有现有对象的大多数(或全部)属性的新对象非常方便,在更新state
咱们就经常这么做:
this.setState(prevState => {
return {foo: {...prevState.foo, a: "updated"}};
});
2
3
# 如何在 ReactJS 的 Props上应用验证?
当应用程序在开发模式下运行时,React 将自动检查咱们在组件上设置的所有 props
,以确保它们具有正确的数据类型。对于不正确的类型,开发模式下会在控制台中生成警告消息,而在生产模式中由于性能影响而禁用它。强制的 props
用 isRequired
定义的。
下面是一组预定义的 prop 类型:
- React.PropTypes.string
- React.PropTypes.number
- React.PropTypes.func
- React.PropTypes.node
- React.PropTypes.bool
例如,咱们为用户组件定义了如下的propTypes
import PropTypes from 'prop-types';
class User extends React.Component {
render() {
return (
<h1>Welcome, {this.props.name}</h1>
<h2>Age, {this.props.age}
);
}
}
User.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# React中constructor和getInitialState的区别?
两者都是用来初始化state的。前者是ES6中的语法,后者是ES5中的语法,新版本的React中已经废弃了该方法。
getInitialState是ES5中的方法,如果使用createClass方法创建一个Component组件,可以自动调用它的getInitialState方法来获取初始化的State对象,
var APP = React.creatClass ({
getInitialState() {
return {
userName: 'hi',
userId: 0
};
}
})
2
3
4
5
6
7
8
React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下:
Class App extends React.Component{
constructor(props){
super(props);
this.state={};
}
}
2
3
4
5
6
# 在 React 中使用构造函数和 getInitialState 有什么区别?
构造函数和getInitialState
之间的区别就是ES6
和ES5
本身的区别。在使用ES6
类时,应该在构造函数中初始化state
,并在使用React.createClass
时定义getInitialState
方法。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { /* initial state */ };
}
}
2
3
4
5
6
等价于:
var MyComponent = React.createClass({
getInitialState() {
return { /* initial state */ };
},
});
2
3
4
5
# 如何有条件地向 React 组件添加属性?
对于某些属性,React 非常聪明,如果传递给它的值是虚值,可以省略该属性。例如:
var InputComponent = React.createClass({
render: function() {
var required = true;
var disabled = false;
return (
<input type="text" disabled={disabled} required={required} />
);
}
});
2
3
4
5
6
7
8
9
10
渲染结果:
<input type="text" required>
另一种可能的方法是:
var condition = true;
var component = (
<div
value="foo"
{ ...( condition && { disabled: true } ) } />
);
2
3
4
5
6
7
# 如何限制作为 props 传递的值的类型,或使其成为必需的?
为了对组件的 props 进行类型检查,您可以使用prop-types
包(以前作为React的一部分包含在15.5版本之前)来声明期望的值类型以及是否需要该prop:
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string,
// 对传递的参数强校验
content: PropTypes.string.isRequired, // 限制为字符串且必传
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 什么是 prop drilling,如何避免?
在构建 React 应用程序时,在多层嵌套组件来使用另一个嵌套组件提供的数据。最简单的方法是将一个 prop
从每个组件一层层的传递下去,从源组件传递到深层嵌套组件,这叫做prop drilling。
prop drilling
的主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护。
为了避免prop drilling
,一种常用的方法是使用React Context。通过定义提供数据的Provider
组件,并允许嵌套的组件通过Consumer
组件或useContext
Hook 使用上下文数据。
有时,可以通过重构组件,避免过早将组件分解为较小的组件以及将公共状态保持在最接近的公共父级中来避免进行钻探。如果您需要在组件树中深处/彼此之间的组件之间共享状态,则可以使用 React 的 Context API (opens new window) 或专用的状态管理库(例如 Redux (opens new window))。
# Refs
# 你对 React 的 refs 有什么了解?
Refs 是 React 中引用的简写。它是一个有助于存储对特定的 React 元素或组件的引用的属性,它将由组件渲染配置函数返回。用于对 render() 返回的特定元素或组件的引用。当需要进行 DOM 测量或向组件添加方法时,它们会派上用场。
class ReferenceDemo extends React.Component{
display() {
const name = this.inputDemo.value;
document.getElementById('disp').innerHTML = name;
}
render() {
return(
<div>
Name: <input type="text" ref={input => this.inputDemo = input} />
<button name="Click" onClick={this.display}>Click</button>
<h2>Hello <span id="disp"></span> !!!</h2>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父组件中,ref:
class Father extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
componentDidMount(){
console.log(this.myRef.current);
}
render(){
return <Child ref={this.myRef}/>
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 列出一些应该使用 Refs 的情况。
以下是应该使用 refs 的情况:
- 需要管理焦点、选择文本或媒体播放时
- 触发式动画
- 与第三方 DOM 库集成
# React 中 refs 干嘛用的?
Refs
提供了一种访问在render
方法中创建的 DOM 节点或者 React 元素的方法。在典型的数据流中,props
是父子组件交互的唯一方式,想要修改子组件,需要使用新的pros
重新渲染它。凡事有例外,某些情况下咱们需要在典型数据流外,强制修改子代,这个时候可以使用 Refs
。
咱们可以在组件添加一个 ref
属性来使用,该属性的值是一个回调函数,接收作为其第一个参数的底层 DOM 元素或组件的挂载实例。
class UnControlledForm extends Component {
handleSubmit = () => {
console.log("Input Value: ", this.input.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={(input) => this.input = input} />
<button type='submit'>Submit</button>
</form>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
请注意,input
元素有一个ref
属性,它的值是一个函数。该函数接收输入的实际 DOM 元素,然后将其放在实例上,这样就可以在 handleSubmit
函数内部访问它。
经常被误解的只有在类组件中才能使用 refs
,但是**refs
也可以通过利用 JS 中的闭包与函数组件一起使用。**
另外值得一提的是,refs 并不是类组件的专属,函数式组件同样能够利用闭包暂存其值:
function CustomForm ({handleSubmit}) {
let inputElement
return (
<form onSubmit={() => handleSubmit(inputElement.value)}>
<input
type='text'
ref={(input) => inputElement = input} />
<button type='submit'>Submit</button>
</form>
)
}
2
3
4
5
6
7
8
9
10
11
# 如何创建 refs
Refs 是使用 React.createRef()
创建的,并通过 ref
属性附加到 React 元素。在构造组件时,通常将 Refs
分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
2
3
4
5
6
7
8
9
或者这样用:
class UserForm extends Component {
handleSubmit = () => {
console.log("Input Value is: ", this.input.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={(input) => this.input = input} /> // Access DOM input in handle submit
<button type='submit'>Submit</button>
</form>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 请说一说Forwarding Refs有什么用
是父组件用来获取子组件的dom元素的,为什么有这个API,原因如下:
this.myRef=React.createRef();
this.myRef.current
2
// 例如有一个子组件和父组件,代码如下子组件为:
class Child extends React.Component{
constructor(props){
super(props);
}
render(){
return <input />
}
}
// 父组件中,ref:
class Father extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
componentDidMount(){
console.log(this.myRef.current);
}
render(){
return <Child ref={this.myRef}/>
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
此时父组件的this.myRef.current的值是Child组件,也就是一个对象,如果用了React.forwardRef,也就是如下
// 子组件
const Child = React.forwardRef((props, ref) => (
<input ref={ref} />
));
// 父组件
class Father extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount() {
console.log(this.myRef.current);
}
render() {
return <Child ref={this.myRef} />
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
此时父组件的this.myRef.current的值是input这个DOM元素
要访问的是一个组件,操作组件里的具体DOM我们就需要用到 React.forwardRef 这个高阶组件,来转发ref,如:
const Index = () => {
const inputEl = useRef(null);
const handleFocus = () => {
inputEl.current.focus();
};
return (
<>
<Child ref={inputEl} />
<button onClick={handleFocus}>Focus</button>
</>
);
};
const Child = forwardRef((props, ref) => {
return <input ref={ref} />;
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Refs 与函数组件
默认情况下,你不能在函数组件上使用 ref
属性,因为它们没有实例:
function MyFunctionComponent() { return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef(); }
render() {
// This will *not* work!
return (
<MyFunctionComponent ref={this.textInput} /> );
}
}
2
3
4
5
6
7
8
9
10
11
12
13
如果要在函数组件中使用 ref
,你可以使用 forwardRef
(opens new window)(可与 useImperativeHandle
(opens new window) 结合使用),或者可以将该组件转化为 class 组件。
不管怎样,你可以在函数组件内部使用 ref
属性,只要它指向一个 DOM 元素或 class 组件:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它 const textInput = useRef(null);
function handleClick() {
textInput.current.focus(); }
return (
<div>
<input
type="text"
ref={textInput} /> <input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 生命周期
# React 16 生命周期的变化
componentWillMount,componentWillReceiveProps, componentWillUpdate
准备废除。理由:主要是16 版本 render 之前的生命周期可能会被多次执行。 static getDerivedStateFromProps和 getSnapshotBeforeUpdate
新增,用于补充上述的生命周期。
# React组件生命周期的阶段是什么?
React 组件的生命周期有三个不同的阶段:
- *初始渲染阶段:*这是组件即将开始其生命之旅并进入 DOM 的阶段。
- *更新阶段:*一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
- *卸载阶段:*这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。
# 详细解释 React 组件的生命周期方法。
一些最重要的生命周期方法是:
- componentWillMount**()** – 在渲染之前执行,在客户端和服务器端都会执行。
- componentDidMount**()** – 仅在第一次渲染后在客户端执行。
- componentWillReceiveProps**()** – 当从父类接收到 props 并且在调用另一个渲染器之前调用。
- shouldComponentUpdate**()** – 根据特定条件返回 true 或 false。如果你希望更新组件,请返回true 否则返回 false。默认情况下,它返回 false。
- componentWillUpdate**()** – 在 DOM 中进行渲染之前调用。
- componentDidUpdate**()** – 在渲染发生后立即调用。
- componentWillUnmount**()** – 从 DOM 卸载组件后调用。用于清理内存空间。
# React 组件生命周期有哪些不同阶段?
在组件生命周期中有四个不同的阶段:
- Initialization:在这个阶段,组件准备设置初始化状态和默认属性。
- Mounting:react 组件已经准备好挂载到浏览器 DOM 中。这个阶段包括
componentWillMount
和componentDidMount
生命周期方法。 - Updating:在这个阶段,组件以两种方式更新,发送新的 props 和 state 状态。此阶段包括
shouldComponentUpdate
、componentWillUpdate
和componentDidUpdate
生命周期方法。 - Unmounting:在这个阶段,组件已经不再被需要了,它从浏览器 DOM 中卸载下来。这个阶段包含
componentWillUnmount
生命周期方法。
除以上四个常用生命周期外,还有一个错误处理的阶段:
Error Handling:在这个阶段,不论在渲染的过程中,还是在生命周期方法中或是在任何子组件的构造函数中发生错误,该组件都会被调用。这个阶段包含了 componentDidCatch
生命周期方法。
# Initialization 初始化
constructor()
:class
的构造函数,并非React独有;
# Mounting 挂载
componentWillMount()
: 在组件即将被挂载到页面的时刻自动执行;render()
: 页面挂载;componentDidMount()
: 组件被挂载到页面之后自动执行;
componentWillMount()
和 componentDidMount()
,只会在页面第一次挂载的时候执行,state变化时,不会重新执行
# Updation 组件更新
shouldComponentUpdate()
: 该生命周期要求返回一个bool
类型的结果,如果返回true
组件正常更新,如果返回false
组件将不会更新;componentWillUpdate()
: 组件被更新之前执行,如果shouldComponentUpdate()
返回false
,将不会被被执行;componentDidUpdate()
: 组件更新完成之后执行;
componentWillReceiveProps()
: props独有的生命周期,执行条件如下:
- 组件要从父组件接收参数;
- 只要父组件的
render()
被执行了,子组件的该生命周期就会执行; - 如果这个组件第一次存在于父组件中,不会执行;
- 如果这个组件之前已经存在于父组件中,才会执行;
# Unmounting 组件卸载
componentWillUnmount()
: 当组件即将被从页面中剔除的时候,会被执行;
# React 的生命周期方法有哪些?
componentWillMount
:在渲染之前执行,用于根组件中的 App 级配置。
componentDidMount
:在第一次渲染之后执行,可以在这里做AJAX请求,DOM 的操作或状态更新以及设置事件监听器。
componentWillReceiveProps
:在初始化render
的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染
shouldComponentUpdate
:确定是否更新组件。默认情况下,它返回true
。如果确定在 state
或 props
更新后组件不需要在重新渲染,则可以返回false
,这是一个提高性能的方法。
componentWillUpdate
:在shouldComponentUpdate
返回 true
确定要更新组件之前件之前执行。
componentDidUpdate
:它主要用于更新DOM以响应props
或state
更改。
componentWillUnmount
:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器。
# React最新的生命周期是怎样的?
React 16之后有三个生命周期被废弃(但并未删除)
- componentWillMount
- componentWillReceiveProps
- componentWillUpdate
官方计划在17版本完全删除这三个函数,只保留UNSAVE_前缀的三个函数,目的是为了向下兼容,但是对于开发者而言应该尽量避免使用他们,而是使用新增的生命周期函数替代它们
目前React 16.8 +的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段
挂载阶段:
- constructor: 构造函数,最先被执行,我们通常在构造函数里初始化state对象或者给自定义方法绑定this
- getDerivedStateFromProps:
static getDerivedStateFromProps(nextProps, prevState)
,这是个静态方法,当我们接收到新的属性想去修改我们state,可以使用getDerivedStateFromProps - render: render函数是纯函数,只返回需要渲染的东西,不应该包含其它的业务逻辑,可以返回原生的DOM、React组件、Fragment、Portals、字符串和数字、Boolean和null等内容
- componentDidMount: 组件装载之后调用,此时我们可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这个里面,但是记得在componentWillUnmount中取消订阅
更新阶段:
- getDerivedStateFromProps: 此方法在更新个挂载阶段都可能会调用
- shouldComponentUpdate:
shouldComponentUpdate(nextProps, nextState)
,有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此生命周期来优化React程序性能 - render: 更新阶段也会触发此生命周期
- getSnapshotBeforeUpdate:
getSnapshotBeforeUpdate(prevProps, prevState)
,这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用 - componentDidUpdate:
componentDidUpdate(prevProps, prevState, snapshot)
,该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。
卸载阶段:
- componentWillUnmount: 当我们的组件被卸载或者销毁了就会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作
# React的请求应该放在哪个生命周期中?【要点】
React的异步请求到底应该放在哪个生命周期里,有人认为在componentWillMount
中可以提前进行异步请求,避免白屏,其实这个观点是有问题的.
由于JavaScript中异步事件的性质,当您启动API调用时,浏览器会在此期间返回执行其他工作。当React渲染一个组件时,它不会等待componentWillMount它完成任何事情 - React继续前进并继续render,没有办法“暂停”渲染以等待数据到达。
而且在componentWillMount
请求会有一系列潜在的问题,首先,在服务器渲染时,如果在 componentWillMount 里获取数据,fetch data会执行两次,一次在服务端一次在客户端,这造成了多余的请求,其次,在React 16进行React Fiber重写后,componentWillMount
可能在一次渲染中多次调用.
目前官方推荐的异步请求是在componentDidmount
中进行.
如果有特殊需求需要提前请求,也可以在特殊情况下在constructor
中请求:
react 17之后
componentWillMount
会被废弃,仅仅保留UNSAFE_componentWillMount
# 生命周期简单使用场景
使用shouldComponentUpdate()
防止页面进行不必要的渲染
//用生命周期进行性能优化
shouldComponentUpdate () {
if (nextProps.content !== this.props.content) {
return true;
}
return false;
}
2
3
4
5
6
7
Ajax 请求页面初始数据componentDidMount()
不能写在render()
之中,因为会重复调用,也不能写在componentWillMount()
之中,会与RN等其它框架冲突,不然也可以写在这里面,同样是只执行一次。
同样也可以写在构造函数constructor()
之中,但是不建议这样做。
import axios from 'axios'
componentDidMount () {
axios.get('/api/todolist').then((res) => {
console.log(res.data);
this.setState(() => ({
list: [...res.data]
}));
}).catch((err) => {
console.log(err);
});
}
2
3
4
5
6
7
8
9
10
11
# 组件通信
# React组件通信如何实现?
React组件间通信方式:
- 父组件向子组件通讯: 父组件可以向子组件通过传 props 的方式,向子组件进行通讯
- 子组件向父组件通讯: props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中
- 兄弟组件通信: 找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
- 跨层级通信:
Context
设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context
通信再适合不过 - 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入event模块进行通信
- 全局状态管理工具: 借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态
# React 组件如何通讯
- 父子组件通过 属性 和 props 通讯
- 通过 context 通讯
- 通过 Redux 通讯
# 异步组件
# React.lazy
// 引入需要异步加载的组件
const LazyComponent = React.lazy(() => import('./lazyDemo') )
// 使用异步组件,异步组件加载中时,显示fallback中的内容
<React.Suspense fallback={<div>异步组件加载中</div>}>
<LazyComponent />
</React.Suspense>
2
3
4
5
6
7
# 组件封装
# 组件公共逻辑的抽离
- Render Props;
- 高阶组件 HOC;
- mixin,已被 React弃用;
# React如何进行组件/逻辑复用?
抛开已经被官方弃用的Mixin,组件抽象的技术目前有三种比较主流:
- 高阶组件:
- 属性代理
- 反向继承
- 渲染属性
- react-hooks
# mixin、hoc、render props、react-hooks的优劣如何?
Mixin的缺陷:
- 组件与 Mixin 之间存在隐式依赖(Mixin 经常依赖组件的特定方法,但在定义组件时并不知道这种依赖关系)
- 多个 Mixin 之间可能产生冲突(比如定义了相同的state字段)
- Mixin 倾向于增加更多状态,这降低了应用的可预测性(The more state in your application, the harder it is to reason about it.),导致复杂度剧增
- 隐式依赖导致依赖关系不透明,维护成本和理解成本迅速攀升:
- 难以快速理解组件行为,需要全盘了解所有依赖 Mixin 的扩展行为,及其之间的相互影响
- 组价自身的方法和state字段不敢轻易删改,因为难以确定有没有 Mixin 依赖它
- Mixin 也难以维护,因为 Mixin 逻辑最后会被打平合并到一起,很难搞清楚一个 Mixin 的输入输出
HOC相比Mixin的优势:
- HOC通过外层组件通过 Props 影响内层组件的状态,而不是直接改变其 State不存在冲突和互相干扰,这就降低了耦合度;
- 不同于 Mixin 的打平+合并,HOC 具有天然的层级结构(组件树结构),这又降低了复杂度;
HOC的缺陷:
- 扩展性限制: HOC 无法从外部访问子组件的 State因此无法通过shouldComponentUpdate滤掉不必要的更新,React 在支持 ES6 Class 之后提供了React.PureComponent来解决这个问题
- Ref 传递问题: Ref 被隔断,后来的React.forwardRef 来解决这个问题
- Wrapper Hell: HOC可能出现多层包裹组件的情况,多层抽象同样增加了复杂度和理解成本
- 命名冲突: 如果高阶组件多次嵌套,没有使用命名空间的话会产生冲突,然后覆盖老属性
- 不可见性: HOC相当于在原有组件外层再包装一个组件,你压根不知道外层的包装是啥,对于你是黑盒
Render Props优点:
- 上述HOC的缺点Render Props都可以解决
Render Props缺陷:
- 使用繁琐: HOC使用只需要借助装饰器语法通常一行代码就可以进行复用,Render Props无法做到如此简单
- 嵌套过深: Render Props虽然摆脱了组件多层嵌套的问题,但是转化为了函数回调的嵌套
React Hooks优点:
- 简洁: React Hooks解决了HOC和Render Props的嵌套问题,更加简洁
- 解耦: React Hooks可以更方便地把 UI 和状态分离,做到更彻底的解耦
- 组合: Hooks 中可以引用另外的 Hooks形成新的Hooks,组合变化万千
- 函数友好: React Hooks为函数组件而生,从而解决了类组件的几大问题:
- this 指向容易错误
- 分割在不同声明周期中的逻辑使得代码难以理解和维护
- 代码复用成本高(高阶组件容易使代码量剧增)
React Hooks缺陷:
- 额外的学习成本(Functional Component 与 Class Component 之间的困惑)
- 写法上有限制(不能出现在条件、循环中),并且写法限制增加了重构成本
- 破坏了PureComponent、React.memo浅比较的性能优化效果(为了取最新的props和state,每次render()都要重新创建事件处函数)
- 在闭包场景可能会引用到旧的state、props值
- 内部实现上不直观(依赖一份可变的全局状态,不再那么“纯”)
- React.memo并不能完全替代shouldComponentUpdate(因为拿不到 state change,只针对 props change)
关于react-hooks的评价来源于官方react-hooks RFC (opens new window)
# HOC
# 什么是高阶组件(HOC)?
高阶组件是重用组件逻辑的高级方法,是一种源于 React 的组件模式。 HOC 是自定义组件,在它之内包含另一个组件。它们可以接受子组件提供的任何动态,但不会修改或复制其输入组件中的任何行为。你可以认为 HOC 是“纯(Pure)”组件。
高阶组件(HOC)是接受一个组件并返回一个新组件的函数。基本上,这是一个模式,是从 React 的组合特性中衍生出来的,称其为纯组件,因为它们可以接受任何动态提供的子组件,但不会修改或复制输入组件中的任何行为。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC 可以用于以下许多用例
- 代码重用、逻辑和引导抽象
- 渲染劫持
- state 抽象和操作
- props 处理
# 你能用HOC做什么?
HOC可用于许多任务,例如:
- 代码重用,逻辑和引导抽象
- 渲染劫持
- 状态抽象和控制
- Props 控制
# 什么是高阶组件HOC
高阶组件不是组件,是 增强函数,可以输入一个元组件,返回出一个新的增强组件
- 属性代理 (Props Proxy) ; 在我看来属性代理就是提取公共的数据和方法到父组件,子组件只负责渲染数据,相当于设计模式里的模板模式,这样组件的重用性就更高了
function proxyHoc(WrappedComponent) {
return class extends React.Component {
render() {
const newProps = {
count: 1
}
return <WrappedComponent {...this.props} {...newProps} />
}
}
}
2
3
4
5
6
7
8
9
10
- 反向继承
const MyContainer = (WrappedComponent)=>{
return class extends WrappedComponent {
render(){
return super.render();
}
}
}
2
3
4
5
6
7
# hoc示范
高阶组件不是一种功能,而是一种模式
// 高阶组件 基本用法
const HOCFactory = (Component) => {
class HOC extends React.Component {
// 在此定义多个组件的公共逻辑
render () {
return <Component {...thi.props} /> // 返回拼装的结果
}
}
return HOC
}
const MyComponent1 = HOCFactory(WrappedComponent1)
const MyComponent2 = HOCFactory(WrappedComponent2)
2
3
4
5
6
7
8
9
10
11
12
实际案例
import React from 'react'
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
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
# Render Props
Render Props 核心思想:通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件
class Factory extends React.Component {
constructor () {
this.state = {
/* 这里 state 即多个组件的公共逻辑的数据 */
}
}
/* 修改 state */
render () {
return <div>{this.props.render(this.state)}</div>
}
}
const App = () => {
/* render 是一个函数组件 */
<Factory render={
(props) => <p>{props.a} {props.b}...</p>
} />
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# this指向
# React 中的箭头函数是什么?怎么用?
箭头函数(=>)是用于编写函数表达式的简短语法。这些函数允许正确绑定组件的上下文,因为在 ES6 中默认下不能使用自动绑定。使用高阶函数时,箭头函数非常有用。
//General way
render() {
return(
<MyInput onChange = {this.handleChange.bind(this) } />
);
}
//With Arrow Function
render() {
return(
<MyInput onChange = { (e)=>this.handleOnChange(e) } />
);
}
2
3
4
5
6
7
8
9
10
11
12
# 为什么类方法需要绑定到类实例?
在 JS 中,this
值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this
引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:
class SubmitButton extends React.Component {
constructor(props) {
super(props);
this.state = {
isFormSubmitted: false
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit() {
this.setState({
isFormSubmitted: true
});
}
render() {
return (
<button onClick={this.handleSubmit}>Submit</button>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 何避免在React重新绑定实例?【要点】
有几种常用方法可以避免在 React 中绑定方法:
1.将事件处理程序定义为内联箭头函数
class SubmitButton extends React.Component {
constructor(props) {
super(props);
this.state = {
isFormSubmitted: false
};
}
render() {
return (
<button onClick={() => {
this.setState({ isFormSubmitted: true });
}}>Submit</button>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2.使用箭头函数来定义方法:
class SubmitButton extends React.Component {
state = {
isFormSubmitted: false
}
handleSubmit = () => {
this.setState({
isFormSubmitted: true
});
}
render() {
return (
<button onClick={this.handleSubmit}>Submit</button>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
3.使用带有 Hooks 的函数组件
const SubmitButton = () => {
const [isFormSubmitted, setIsFormSubmitted] = useState(false);
return (
<button onClick={() => {
setIsFormSubmitted(true);
}}>Submit</button>
)
};
2
3
4
5
6
7
8
9