1. setState
setState更新状态的2种写法
方式一:setState(stateChange, [callback])——对象式的setState
- stateChange为状态改变对象(该对象可以体现出状态的更改)
- callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
方式二:setState(updater, [callback])——函数式的setState
- updater为返回stateChange对象的函数
-
updater可以接收到state和props
- callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结
对象式的setState是函数式的setState的简写方式(语法糖)
使用原则
以下原则只是建议性的,不是强制性的要求
-
如果新状态不依赖于原状态 ===>
使用对象方式
-
如果新状态依赖于原状态 ===>
使用函数方式
-
如果需要在
setState()
执行后获取最新的状态数据,要在第二个callback函数中读取
setState同步VS异步问题
setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
- 合成事件:就是react 在组件中的onClick等都是属于它自定义的合成事件
- 原生事件:比如通过addeventListener添加的,dom中的原生事件
setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
![]()
- 从上图可知,在react中,setState通过一个队列机制实现state的更新。当执行setState时,会把需要更新的state合并后放入状态队列,而不会立刻更新
this.state
,当进入组件可更新状态时,这个队列机制就会
高效的批量的更新state
。
import React, { Component } from 'react'
export default class Demo extends Component {
state = {
count: 0
}
add = () => {
//方式一:对象式的setState
// //1.获取原来的count值
// const { count } = this.state
// //2.更新状态
// this.setState({ count: count + 1 }, () => {
// console.log(this.state.count);
// })
// //假如setState更新完数据后,立即使用的话,会出现问题
// //console.log('获取最新的count数据为',this.state.count); //0
//方式二:函数式的setState
this.setState(state => ({ count: state.count + 1 }))
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<button onClick={this.add}>点我呀+1</button>
</div>
)
}
}
2. lazyLoad(懒加载组件)
路由组件的lazyLoad
-
步骤一:通过React的
lazy
函数配合
import()
函数动态加载路由组件 ===> 路由组件代码会被分开打包const Login = lazy(()=>import('@/pages/Login'))
-
步骤二:通过
<Suspense>
指定在加载得到路由打包文件前显示一个自定义loading界面<Suspense fallback={<h1>loading.....</h1>}> <Switch> <Route path="/xxx" component={Xxxx}/> <Redirect to="/login"/> </Switch> </Suspense>
3. Hooks
1. React Hook/Hooks是什么?
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以在函数组件中使用 state 以及其他的 React 特性
2. 三个常用的Hook
-
State Hook:
React.useState()
-
Effect Hook:
React.useEffect()
-
Ref Hook:
React.useRef()
2.1. State Hook
- State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
-
语法:
const [xxx, setXxx] = React.useState(initValue)
-
useState()说明
-
参数:
第一次初始化指定的值在内部作缓存
-
返回值:
包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
-
参数:
-
setXxx()两种写法
-
setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值 -
setXxx(value => newValue)
: 参数为函数,
接收原本的状态值, 返回新的状态值
, 内部用其覆盖原来的状态值
-
2.2. Effect Hook
-
Effect Hook
可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子) -
React中的副作用操作
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
-
语法和说明
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 } }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
-
说明
可以把
useEffect Hook
看做如下三个函数的组合-
componentDidMount()
-
componentDidUpdate()
-
componentWillUnmount()
-
5. Ref Hook
-
Ref Hook
可以在函数组件中存储/查找组件内的标签或任意其它数据 -
语法
const refContainer = useRef()
-
作用
保存标签对象,功能与
React.createRef()
一样
4. Fragment(避免额外标记)
-
概述
- 使用 Fragment 减少了包含的额外标记数量,这些标记只是为了满足在 React 组件中具有公共父级的要求。
- 在创建新组件时,每个组件应具有单个父标签。父级不能有两个标签,所以顶部要有一个公共标签。所以经常在组件顶部添加额外标签
-
以将元素包含在
片段
(fragement)中。片段不会向组件引入任何额外标记,但它仍然为两个相邻标记提供父级,因此满足在组件顶级具有单个父级的条件
-
使用方式
<Fragment><Fragment> <></>
-
作用
可以不用必须有一个真实的DOM根标签了
-
<Fragment>
与
<>
区别-
<></>
语法不能接受键值或属性,
<Fragment>
可以 -
使用显式
<Fragment>
语法声明的片段可能具有 key。key 是唯一可以传递给 Fragment 的属性
-
import React, { Component, Fragment } from 'react'
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text" />
<input type="text" />
</Fragment>
)
}
}
5. Context
使用Context的原因
-
为了有时候想传递数据通过组件树,但是不想给每一层级的组件手动传递属性,那么
context
就能实现
跨级
传递数据到组件树中你想传递到的深层次组件 -
有时候
A组件
为了给
B组件
中的
C组件
传递一个
prop
,而需要把参数在组件中传递两次才能最终将
A组件
中的
prop
传递给
C组件
-
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用方式
-
创建Context容器对象
const XxxContext = React.createContext()
-
渲染子组件时,外面包裹
xxxContext.Provider
,通过
value
属性给后代组件传递数据<xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
-
后代组件读取数据
//第一种方式:仅适用于类组件 static contextType = xxxContext // 声明接收context this.context // 读取context中的value数据 //第二种方式: 函数组件与类组件都可以 <xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
示例
.parent{
width: 500px;
background-color: orange;
padding: 8px;
}
.child{
width: 100%;
background-color: skyblue;
padding: 8px;
}
.grand{
width: 100%;
background-color: gray;
padding: 8px;
}
import React, { Component, createContext, useContext } from 'react'
import './index.css'
//创建Context对象
const MyContext = createContext()
const { Provider, Consumer } = MyContext
export default class A extends Component {
state = { username: 'Jack', age: 18 }
render() {
const { username, age } = this.state
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C />
</div>
)
}
}
// 方式一
/* class C extends Component {
//声明接收context
static contextType = MyContext
render() {
const {username,age} = this.context
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是{age}</h4>
</div>
)
}
} */
//方式二
/* function C() {
const contextData = useContext(MyContext)
const { username, age } = contextData
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:{username},年龄是=={age}</h4>
</div>
)
} */
//方式三
function C() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>我从A组件接收到的用户名:
<Consumer>
{value => `${value.username},年龄是${value.age}`}
</Consumer>
</h4>
</div>
)
}
6. 组件优化
概述
-
浏览器的
重绘和重排版(reflows & repaints)
(DOM操作都会引起)才是导致网页性能问题的关键。而React
虚拟DOM
的目的就是为了减少浏览器的
重绘和重排版
-
组件渲染方式有两种
初始渲染
和
更新渲染
,而我们需要优化的地方就是
更新渲染
-
组件更新生命周期中必调用
shouldComponentUpdate
(组件是否应该更新)。
shouldComponentUpdate
默认返回
true
,必更新。所有当我们判断出组件没必要更新是,
shouldComponentUpdate
可以返回
false
,就达到优化效果
Component的2个问题
-
只要执行
setState()
,即使不改变状态数据,组件也会重新
render()
==>
效率低
-
只当前组件重新render(),就会自动重新render
子组件
,纵使子组件没有用到父组件的任何数据 ==>
效率低
效率高的做法
-
只有当组件的state或props数据发生改变时才重新render()
原因
-
Component中的
shouldComponentUpdate()
总是返回
true
。
shouldComponentUpdate
是决定
react
组件什么时候能够不重新渲染的函数,但是这个函数默认的实现方式就是简单的返回一个
true
。也就是说,默认每次更新的时候都要调用所用的生命周期函数,包括
render
函数,重新渲染。
解决
方式一
-
重写
shouldComponentUpdate()
方法 - 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
方式二
-
使用
PureComponent
-
PureComponent重写了
shouldComponentUpdate()
, 只有state或props数据有变化才返回true -
注意事项
- 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
- 不要直接修改state数据, 而是要产生新数据
- 项目中一般使用PureComponent来优化
示例
-
样式
.parent{ background-color: orange; padding: 10px; } .child{ background-color: gray; margin-top: 30px; padding: 10px; }
-
js代码
import React, { PureComponent } from 'react' import './index.css' export default class Parent extends PureComponent { state = { carName: "奔驰c36", stus: ['小张', '小李', '小王'] } addStu = () => { const { stus } = this.state this.setState({ stus: ['小刘', ...stus] }) } changeCar = () => { //this.setState({carName:'迈巴赫'}) const obj = this.state obj.carName = '迈巴赫' console.log(obj === this.state); this.setState(obj) } /* shouldComponentUpdate(nextProps,nextState){ console.log("目前的props和state===",this.props,this.state); //目前的props和state console.log("接下要变化的目标props,目标state===",nextProps,nextState); //接下要变化的目标props,目标state return !this.state.carName === nextState.carName } */ render() { console.log('Parent---render'); const { carName,stus } = this.state return ( <div className="parent"> <h3>我是Parent组件</h3> {stus} <span>我的车名字是:{carName}</span><br /> <button onClick={this.changeCar}>点我换车</button> <button onClick={this.addStu}>添加一个小刘</button> <Child carName="奥拓" /> </div> ) } } class Child extends PureComponent { /* shouldComponentUpdate(nextProps,nextState){ console.log(this.props,this.state); //目前的props和state console.log(nextProps,nextState); //接下要变化的目标props,目标state return !this.props.carName === nextProps.carName } */ render() { console.log('Child---render'); return ( <div className="child"> <h3>我是Child组件</h3> <span>我接到的车是:{this.props.carName}</span> </div> ) } }
7. 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组件内的数据, ==>
以上方式是无法做到的
render props
<A render={(data) => <C data={data}></C>}></A>
- A组件: {this.props.render(内部state数据)}
- C组件: 读取A组件传入的数据显示 {this.props.data}
8. 错误边界
概述
-
在 UI 部分发生的 JavaScript 异常不应该阻断整个应用。为了解决这一问题,React 16 引入了“错误边界(error boundary)”这一新概念。
-
错误边界作为 React 组件,用以
捕获在子组件树中任何地方的 JavaScript 异常,打印这些错误,并展示备用 UI
而非让组件树崩溃。错误边界会捕获渲染期间,在生命周期方法中以及在其整个树的构造函数中的异常。 -
错误边界(
Error boundary
):用来捕获后代组件错误,渲染出备用页面 -
错误边界仅可以捕获组件树中的后代组件错误
。一个错误边界无法捕获其自身的错误。若错误边界在渲染错误信息时失败,则该错误会传递至上一层最接近的错误边界
特点
- 只能捕获后代组件生命周期产生的错误
- 不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式
getDerivedStateFromError
配合
componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
示例
-
子组件代码
import React, { Component } from 'react' export default class Child extends Component { state = { users: [ { id: '001', name: 'tom', age: 18 }, { id: '002', name: 'jack', age: 19 }, { id: '003', name: 'peiqi', age: 20 }, ] //错误边界演示数据 // users:'abc' } render() { return ( <div> <h2>我是Child组件</h2> { this.state.users.map((userObj) => { return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4> }) } </div> ) } }
-
父组件代码
import React, { Component } from 'react' import Child from './Child' export default class Parent extends Component { state = { hasError: '' //用于标识子组件是否产生错误 } //当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息 static getDerivedStateFromError(error) { console.log('@@@', error); return { hasError: error } } // React 16 将提供一个内置函数 componentDidCatch,如果 render() 函数抛出错误,则会触发该函数 // componentDidCatch 是包含错误堆栈的 info 对象 componentDidCatch(error, info) { console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决'); console.log('object error:>> ', error); console.log('object info:>> ', info); console.log('错误堆栈:>> ', info.componentStack); } render() { return ( <div> <h2>我是Parent组件</h2> {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />} </div> ) } }