React - 进阶知识
# setState函数式状态
# setState更新状态的2种写法
setState(stateChange, [callback])
对象式的 setState(常用)stateChange
为状态改变对象(该对象可以体现出状态的更改)callback
是可选的回调函数,它在状态更新完毕、界面也更新后(render 调用后)才被调用const {count} = this.state // 更新状态 this.setState({count:count+1},()=>{ console.log(this.state.count); // 打印出来时 更新后的数据 }) // console.log('12行的输出',this.state.count); // 异步,所以打印出来的 count 是没 +1 之前的数据
1
2
3
4
5
6setState(updater, [callback])
函数式的 setStateupdater
为返回 stateChange 对象的函数。updater
可以接收到 state 和 props。callback
是可选的回调函数,它在状态更新、界面也更新后(render 调用后)才被调用。
# 总结
对象式的 setState 是函数式的 setState 的简写方式(语法糖)。
使用原则:
如果新状态不依赖于原状态 ===> 使用对象方式
如果新状态依赖于原状态 ===> 使用函数方式
如果需要在
setState()
执行后获取最新的状态数据,要在第二个 callback 函数中读取
this.setState( (state,props) => ({count:state.count+1}),()=>{})
# lazyLoad懒加载和Suspense
# lazyLoad
懒加载作用:初始页面不会全部加载懒加载包裹的组件,当点击组件时候再去加载,第二次再点击相同的懒加载包裹的组件就没有用了。
具体:
import { lazy } from 'react'
// 不使用lazy,原先的import
// import xxx from '组件'
// 使用lazy替换原来的import
const xxx = lazy(function(){
import('组件')
})
2
3
4
5
6
7
8
9
# Suspense
作用:Suspense 包括的组件如果还在渲染或者渲染失败的时候,显示自定义组件(Loading,404 等)
具体:<Suspense fallback={自定义组件}> 组件内容 </Suspense>
import React, { Component,lazy,Suspense} from 'react'
// 1.通过 React 的 lazy 函数配合 import() 函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
......
// 2.通过 <Suspense> 指定在加载得到路由打包文件前显示一个自定义 loading 界面
render() {
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
}
2
3
4
5
6
7
8
9
10
11
12
13
# Hooks函数式
# React Hook/Hooks是什么?
- Hook 是 React 16.8.0 版本增加的新特性/新语法
- 可以让你在 函数组件中使用 state 、生命周期函数、ref 以及其他的 React 特性
# 三个常用的Hook
State Hook: React.useState()
Effect Hook: React.useEffect()
Ref Hook: React.useRef()
# State Hook
State Hook 让函数组件也可以有 state 状态,并进行状态数据的读写操作
语法:
const [xxx, setXxx] = React.useState(initValue)
useState()
说明:参数:第一次初始化指定的值在内部作缓存,即初始化的 state 的 value 值
返回值: 包含 2 个元素的数组,第 1 个为内部当前 state 状态 key,第 2 个为更新状态值的函数
setXxx()
的 2 种写法:setXxx(newValue)
: 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值setXxx(value => newValue)
: 参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
下列需求:初始化 count 值为 0,点击一次加 1
import React from 'react'
function Demo(){
const [count,setCount] = React.useState(0) // 初始化 count=0,存入 state 里
add(){
// 第一种
// setXxx(count+1)
// 第二种
setCount(count => count+1 )
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
</div>
)
}
export default Demo
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
React 中的副作用操作:
发 ajax 请求数据获取
设置订阅 / 启动定时器
手动更改真实 DOM
语法和说明: (useEffect 接收两个参数)
参数一:为函数,如果没有第二个参数,则代表
componentDidMount
和componentDidUpdate
两个生命函数该函数可以有返回值,返回值为函数,且该函数其实就是
componentWillUnmount
生命函数参数二:为数组,可指定监测哪个 state 里的数据
比如监测 state 的 count 和 name 属性,则 ['count','name](通过
componentDidUpdate
生命函数)数组为空,则代表所有数据都不监听,即只有
componentDidMount
生命函数被调用
import React from 'react' useEffect(() => { // 在此可以执行任何带副作用操作 ...... return () => { // 在组件卸载前执行 // 在此做一些收尾工作,比如清除定时器/取消订阅等 ...... } }, [stateValue]) // 如果指定的是[],回调函数只会在第一次render()后执行
1
2
3
4
5
6
7
8
9可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount() componentDidUpdate() componentWillUnmount()
1
2
3
需求:页面挂载完,定时器让 count 每秒 +1,销毁前清除定时器
import React from 'react'
import ReactDOM from 'react-dom'
function Demo(){
const [count,setCount] = React.useState(0)
React.useEffect(()=>{
let timer = setInterval(()=>{
setCount(count => count+1 )
},1000) // 页面一挂载就执行该操作,componentDidMount生命函数
return ()=>{// 页面即将卸载实行该操作,componentWillUnmount生命函数
clearInterval(timer)
}
},[]) // [] 代表不使用 componentDidUpdate 生命函数,去掉 [] 代表 componentDidMount 生命函数和 componentDidUpdate 生命函数都使用
// 加的回调
function add(){
// setCount(count+1) //第一种写法
setCount(count => count+1 )
}
// 卸载组件的回调
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
</div>
)
}
export default Demo
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
# Ref Hook
- Ref Hook 可以在函数组件中存储 / 查找组件内的标签或任意其它数据
- 语法:
const refContainer = React.useRef()
,和const refContainer = React.createRef()
类似 - 作用:保存标签对象,功能与
React.createRef()
一样
import React from 'react'
function Demo(){
const myRef = React.useRef()
function show(){
alert(myRef.current.value)
}
return (
<div>
<input type="text" ref={myRef}/>
<button onClick={show}>点我提示数据</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
# Fragment标签碎片
# 背景
render(){return()}
的 return 里必须有一个根标签包裹所有标签,渲染时候这个根标签也会被渲染,如果不想根标签被渲染,使用 Fragment 代替。
# 使用
<Fragment key={xx}> { /* 只接受 key,可写,可不写,遍历数组需要些 */ }
// 真正需要的标签们
</Fragment>
// 另一种写法,空标签
<>
// 真正需要的标签们
<>
2
3
4
5
6
7
8
# 作用
可以不用必须有一个真实的 DOM 根标签了,即 Fragment 可以代替一个 div,且渲染时候 Fragment 会被去掉。
# Context通信
# 理解
一种组件间通信方式,常用于【祖组件】与【后代组件】间通信,和 Vue 的 provide 和 inject 一样。
# 使用
创建 Context 容器对象:(在类组件或者函数组件外面定义)
const XxxContext = React.createContext()
1渲染子组件时,外面包裹
xxxContext.Provider
,通过 value 属性给后代组件传递数据:<xxxContext.Provider value={数据}> // 子组件 </xxxContext.Provider>
1
2
3后代组件读取数据:
第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收 context this.context // 读取 context 中的 value 数据
1
2第二种方式:函数组件与类组件都可以
<xxxContext.Consumer> { value => ( // value 就是 context 中的 value 数据 // 要显示的内容 ) } </xxxContext.Consumer>
1
2
3
4
5
6
7
# 注意
在应用开发中一般不用 Context,一般都用它的封装 React 插件。
# PureComponent组件优化
# Component目前的2个问题
只要执行
setState()
,即使不改变状态数据,组件也会重新render()
==> 效率低只当前组件重新
render()
,就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
# 效率高的做法
只有当组件的 state 或 props 数据发生改变时才重新 render()
。
# 原因
Component 中的 shouldComponentUpdate()
总是返回 true。
# 解决
办法 1:重写
shouldComponentUpdate()
方法(手写)- 比较新旧 state 或 props 数据,如果有变化才返回 true,如果没有返回 false
shouldComponentUpdate(nextProps,nextState){ return !this.state.carName === nextState.carName }
1
2
3办法 2:使用
PureComponent
(官方封装)- PureComponent 重写了
shouldComponentUpdate()
,只有 state 或 props 数据有变化才返回 true
注意:
只是进行 state 和 props 数据的浅比较,如果只是数据对象内部数据变了,返回 false
不要直接修改 state 数据,而是要产生新数据(使用新数据替换原来数据,而不是原来数据的修改)
项目中一般使用 PureComponent 来优化
import React, { PureComponent } from 'react' // 原先 export default class Parent extends Component {} // 使用 PureComponent export default class Parent extends PureComponent {}
1
2
3
4
5- PureComponent 重写了
# render props传入标签内容
# 如何向组件内部动态传入带内容的结构(标签)?
Vue 中:
使用 Slot 技术,也就是通过组件标签体传入结构
<A><B/></A>
React 中:
使用 children props:通过组件标签体传入结构
使用 render props:通过组件标签属性传入结构,而且可以携带数据,一般用 render 函数属性
# children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
// 问题: 如果 B 组件需要A组件内的数据 ==> 做不到
2
3
4
5
# render props
- A 组件:
{this.props.render(内部 state 数据)}
往父组件传数据 - C 组件: 在父组件读取 A 组件传入的数据显示
{this.props.data}
<A render={(data) => <C data={data}></C>}></A>
整体代码:(A 组件和 B 组件是父子关系,但是 A 组件内不写 B 组件,而是通过父组件 Parent 给 A 和 B 进行父子组件绑定,需要 A 组件把数据传给父组件 Parent,父组件 Parent 再把数据传给 B 组件)
import React, { Component } from 'react'
import C from '../1_setState'
export default class Parent extends Component { // 父组件 Parent
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<A render={(name)=><C name={name}/>}/>
</div>
)
}
}
class A extends Component { // 子组件 A
state = {name:'tom'}
render() {
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
{this.props.render(name)}
</div>
)
}
}
class B extends Component { //子组件 B
render() {
return (
<div className="b">
<h3>我是B组件,{this.props.name}</h3>
</div>
)
}
}
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
# 错误边界
# 理解
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面,解决后代组件出现错误,导致整个组件都出现错误扩散。
# 特点
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。
# 使用方式
getDerivedStateFromError
配合 componentDidCatch
,前者进行错误捕获,后者进行错误统计。
错误捕获:然后修改管理错误的数据,一旦错误的数据进行更新,说明出现错误,则出现错误的组件不再显示,显示自定义组件(使用三目运算进行判断)。
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在 render 之前触发
// 返回新的 state
return {
hasError: error,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 组件通信方式总结
# 组件间的关系
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
# 几种通信方式
props:
- children props
- render props
消息订阅-发布:
- pubs-sub、event 等等
集中式管理:
- redux、dva 等等
conText:
- 生产者-消费者模式
# 比较好的搭配方式
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)