React知识点汇总(高级特性)

  • 不是每个都很常用,但用到的时候必须要知道
  • 考察候选人对React的掌握是否全面,且有深度
  • 考察做过的项目是否有深度和复杂度(至少能用到高级特性)

函数组件

函数组件

  • 函数组件是 纯函数,输入props,输出JSX
  • 没有实例(自然也没有this)没有生命周期函数与state,所以性能好
  • 不能扩展其他方法
  • 函数组件,即 无状态组件,更多知识可参考“Redux进阶(1)UI组件、容器组件、无状态组件”
  • 当一个组件中只有render函数时,我们可以使用 无状态组件 替换 它。
  • 使用方法:使用常量const定义一个箭头函数,该函数接收一个参数props(来自父组件的变量、方法),返回原本render函数中的内容(记得输出)

ref属性

  • 作用:获取DOM元素,具体例子在下方“非受控组件”中
  • ref可参考“React高级内容(2)”
  • ref属性值 必须是通过React.create()创建的对象
  • 三个地方名字(refName)一致才能获取DOM节点
    • 创建ref:this.refName = React.createRef()(构造函数中)
    • 使用ref:<input defaultValue={this.state.name} ref={this.refName}/>(render函数中)
    • 通过 ref 获取 DOM 节点(elem):const elem = this.refName.current(事件函数中)

非受控组件

  • 非受控组件:组件值不受state控制(比如:input的值不受state控制),只是使用state给组件赋予了初始值(比如:input的defaultValue属性),由于赋予初始值以后组件值就和state无关了,所以后面想获取 组件值 就只能 通过ref获取DOM元素 的值的方式来操作了。(可回顾“React知识点汇总(基础使用1)”中的 受控组件(value/checked)
  • 使用场景:由于非受控组件需要搭配ref去操作DOM来获取值,所以按照react设计初衷是不推荐使用的,但是特殊时候只能使用 非受控组件,比如
    • 必须手动操作DOM元素,setState实现不了的时候
    • 文件上传<input type=file>(可参考MDN input file使用方法,可以看到想要获取上传的文件的的 File对象 是需要 操作DOM元素的
    • 某些富文本编辑器,需要传入DOM元素(做加粗、加下划线等操作 )
  • 受控组件vs非受控组件优先使用受控组件,这样符合React设计原则。必须操作DOM时,再使用非受控组件

input的 defaultValue属性

  • 注意:区别react中input的 defaultValue属性 和 value属性
    • react中input的defaultValue就相当于原生DOM中的value属性,这样写出来的组件,其value值就是用户输入的内容,和react没有关系,完全不管输入的过程。
    • 而react中input的value属性必须使用onChange来监听这个input,使state和input关联(即 受控组件)
    • 如果在使用input时只需要获取model里的值时,使用 defaultValue就可以了。如果需要获取model的值并且还需要改变它的时候,就需要使用value结合onChange事件与state联系就可以了。(但还是尽量使用value,毕竟defaultValue需要结合ref来获取DOM元素,而react的设计初衷就是尽量不要操作DOM,通过state来带动DOM)
  • 例子:非受控组件例子

checkbox的defaultChecked属性

非受控组件例子

上传文件【必须使用非受控组件】

  • 可参考MDN input file使用方法,可以看到想要获取上传的文件的的 File对象 是需要 操作DOM元素的,所以就必须使用非受控组件。
  • 上传文件【必须使用非受控组件】

Protals(传送门 让组件渲染到父组件以外)

  • 组件默认会按照既定层次嵌套渲染,但我们可以使用Protals让组件渲染到父组件以外
  • 在react中的组件排布位置是不变的(不会改变组件解构),只是在渲染的时候做了一个hack使相关组件渲染到我们指定的地方。
  • Portals使用场景:
    1. 父组件是BFC时,会限制子组件的布局,此时可使用Protals将子组件脱离BFC父组件(比如overflow:hidden
    2. 父组件z-index值太小时,将父组件往外放
    3. fixed元素需要放在body第一层级(DOM树最外层),有更好的浏览器兼容性
  • 总之,Protals一般在CSS布局方面使用

例子(fixed元素放在body第一层级)

  • 正常渲染时:正常渲染时(注意:this.props.children可获取的内容)正常渲染时的结果可以看到固定定位的元素位于DOM树内层
  • 使用 Portals 渲染到 body 上:使用 Portals 渲染到 body 上使用 Portals 结果

context(传递公共信息)

  • 公共信息(语言、主题)如何传递给每个组件?用props太繁琐,用redux小题大做,使用context将 公共信息(语言、主题)如何传递给每个组件
  • 例子:在这里只是假设theme为需要使用的主题,在这个例子中,从外向内是App组件(最外层组件)=》Toolbar组件(中间组件)=》ThemedButton组件(class 组件)、ThemeLink组件(函数组件)
  1. 父组件做context生产方,创建context,并使用context包裹子组件给子组件获取context的能力:创建context父组件App中给子组件获取context的能力
  2. 中间的组件不需要指明往下传递 theme :中间的组件
  3. 子组件做context消费方,可以获取到父组件中传递的context。
    • class子组件 直接使用this.context可获取父组件设置的context: class子组件 使用父组件context的方法
    • 函数子组件 由于没有实例导致没有this,所以:函数子组件 使用父组件context的方法
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
import React from 'react'

// 创建 Context 填入默认值(任何一个 js 变量,这里用字符串)
const ThemeContext = React.createContext('light')

// 底层组件 - 函数是组件
function ThemeLink (props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this

// 函数式组件可以使用 Consumer
return <ThemeContext.Consumer>
{ value => <p>link's theme is {value}</p> }
</ThemeContext.Consumer>
}

// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 可用ES6 static,也可用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
return <div>
<p>button's theme is {theme}</p>
</div>
}
}
ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
)
}

class App extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
render() {
return <ThemeContext.Provider value={this.state.theme}>
<Toolbar />
<hr/>
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.Provider>
}
changeTheme = () => {
this.setState({
theme: this.state.theme === 'light' ? 'dark' : 'light'
})
}
}

export default App

异步组件(懒加载)

  • 使用场景组件比较大 或者需要 懒加载 时,我们可以使用 异步组件来做(性能优化
  • vue中可用import(),react中可用React.lazy+React.Suspense(见下方例子)或者使用react-loadable模块
  • 例子:

【重点】性能优化

  • 比起vue,性能优化对于React更加重要。因为react本身设计的shouldComponentUpdate默认是返回true的,也就是说父组件一更新就会带着所有子组件重新渲染,不管子组件是否有数据变化(是否需要重新渲染)。
  • 回顾讲setState时重点强调的不可变值
  • 措施:
    1. 通过shouldComponentUpdate()设置所接受的参数(新的Props和State值)来判断何时返回false,避免不必要的子组件渲染(注意**一定要搭配state不可变值,原因见下**)
    2. state数据设计为浅层(不要出现二维数组之类的),避免深拷贝或者深度比较(他们需要一次性递归,很消耗性能)
    3. class组件 使用 PureComponent ,它自带浅比较,不需要我们手写shouldComponentUpdate()【需要将state数据设计为浅层】
    4. 函数组件 使用 React.memo,可用 自定义函数 代替shouldComponentUpdate(),作为参数2传入React.memo(),解决函数组件没有生命周期函数的问题。通过React.memo将返回一个新的函数组件,它自带浅比较决定是否重新渲染
    5. immutable.js可生成不可变的对象,基于共享数据(不是深拷贝),速度快性能好,但有一定学习和迁移成本,按需使用。(补充:redux-immutable库的combineReducers()可使合成各个组件的reducer得到的就是immutable对象)
    6. 不是任何时候都需要使用性能优化,不卡顿可不用

shouldComponentUpdate(简称SCU)

  • React默认机制:父组件有更新,(不管子组件是否有改变)子组件也无条件也更新!!!所以我们需要优化react,避免不必要重复渲染的子组件被带着渲染来影响性能。
  • 如不设置shouldComponentUpdate 则默认返回true,代表可以渲染shouldComponentUpdate默认返回true,代表可以渲染
  • 使用shouldComponentUpdate做 性能优化:shouldComponentUpdate基本用法shouldComponentUpdate()会接收新的Props和State值作为参数,并且给了我们权利去决定什么时候返回false来拒绝重复渲染
  • SCU优化一定要每次都用吗?其实不是,需要的时候才优化,如果页面不卡顿就可以不用,一切以开发为主

SCU一定要配合state不可变值

  • 原因:如果state不是不可变值,即它在setState前被改变了,则SCU中拿到的nextState和state是相同的,导致该重新渲染时被判断为不可重新渲染
    lodash库的isEqual可进行深度比较,对于引用类型,**===是会比较地址的**,而 lodash库的isEqual 则只是比较内容物是否相等)
  • 例子:例子
  • 总结:所以SCU一定要配合state不可变值才能保证不会出错
  • 注意:深度比较 很耗费性能,需要进行一次性递归到底,设计state时设计的浅一些来避免使用深度比较会好一些(React的PureComponent和 React.memo就实现了自带浅比较来决定是否重新渲染)

PureComponent 和 React.memo

  • React的PureComponent(纯组件)实现了自带浅比较,它只对比state和props数据的第一层,比起 深度比较 性能更好。
  • 注意:PureComponent也必须要搭配state不可变值(即不能改变state本身),否则坑很多
  • class组件 继承PureComponent,不需要手写SCU,它自带SCU并实现了浅比较PureComponent的例子
  • memo,即 函数组件 中的PureComponentmemo的使用方法可用 自定义函数 代替shouldComponentUpdate(),解决函数组件没有生命周期函数的问题
  • 浅比较已适用大部分情况(尽量不要做深度比较),这就需要我们在react中设计state时尽量扁平,不要一层套一层

不可变值immutable.js

按需使用 & state层级

  • 不是任何时候都需要使用性能优化,不卡顿可不用
  • state层级设计的浅一些可避免深拷贝/深度比较等耗费性能的操作

组件公共逻辑的抽离

  • mixin已被React弃用
  • 高阶组件HOC Vs Render Props 按需使用即可
    • 高阶组件HOC:模式简单,但会增加组件层级(需要考虑透传props以及是否会被覆盖的问题)
      • 总结:在高阶组件函数内自定义公共逻辑(包含逻辑的class组件B 包含 负责渲染的原函数组件A),高阶组件函数 接受 负责渲染的组件A(一般是函数组件),返回 涵盖逻辑的新组件B(class组件)
      • 注意:在高阶组件函数内引用原组件A时需要透传所有props,以此保证别的组件调用新的高阶组件B时传给B的属性/方法 可被原组件A调用
    • Render Props代码简洁,学习成本较高
      • 总结(原组件A 包含 负责逻辑的class组件B 包含 负责渲染的函数组件C。)可复用的是自定义的class组件B,它返回函数组件C。原组件A(一般是函数组件)通过调用 class组件B 并向 子组件B 传递 属性(函数组件) 的方式定义需要渲染的 函数组件C

高阶组件HOC

简单示例

  • 高阶组件不是一种功能,是一种模式,他是一个函数,这个函数接受一个组件并返回一个新组件
    • 高阶组件负责公共逻辑,传入的组件 传出后 生成的新组件 就会带上这部分逻辑
    • 定义高阶组件时 需要向 传入的组件 传递自己的所有props(包括别人传过来的),这样传入的组件才能获取使用(详情见下面例子)
  • 高阶组件的定义:创建一个函数,接受一个组件A,在函数内创建组件B将组件A包裹并给他逻辑,最后返回组件B。

例子

  • 实现效果:实现效果鼠标划过时显示鼠标位置
  • 思路:假设很多组件都要使用这个逻辑,那么怎么将这个逻辑抽离出来?在 高阶组件 的函数中实现鼠标滑动的逻辑,App组件(函数组件)只需要负责渲染,最终返回的是 App组件通过高阶组件来返回的新组件(已经包含了逻辑的App组件)
  • 代码:定义高阶组件使用高阶组件

react-redux connect

react-redux connect也是高阶组件
redux connect

  1. connect(函数1,函数2)返回的是高阶组件的函数(回顾:redux-thunk中间件使action可以为函数)
  2. 在调用1返回的函数并将组件TodoList传入,得到的就是新的组件

render props

  • 高阶组件高阶组件(负责逻辑)包含组件A(负责渲染)(即 逻辑包含渲染);render props组件A包含class组件,class组件(负责逻辑)中包含函数组件(负责渲染(即 逻辑包含渲染)。
  • 区分 高阶组件 和 render props 的包裹关系:区分高阶组件和render props的包裹关系
    • 思路:
      1. 左图定义一个class组件Factory,在class组件中将state作为参数传给 函数(组件)render注意:class组件需要设置propTypes来规定必须接收一个 render 属性,而且是函数(具体见下方例子))
      2. 右图是调用class组件Factory时,在render属性上定义一个函数组件render,该组件通过参数获取到父组件Factory中传过来的数据(state)
  • 例子:
    • 实现效果:实现效果鼠标划过时显示鼠标位置
  • 代码:render props例子代码1render props例子代码2获取App的父组件的值的方法比使用 高阶组件 要简单,毕竟App组件就是最外层,不需要透传所有props
,