一、Context与ContextType
Context与ContextType用于解决编程开发效率问题。
(一)、Context
1、定义
Context提供一种方式,能够让数据在组件树中传递而不必一级一级手动传递。
Context由生产者
Provider
和消费者Consumer
组成。有点类似于我们定义的全局变量。Context结构
1、基本使用
import React, { Component,createContext } from 'react';
+const BatteryContext = createContext()
class Leaf extends Component{
render(){
return (
+ <BatteryContext.Consumer>
+ {
+ battery => <h1>Battery: {battery}</h1>
+ }
+ </BatteryContext.Consumer>
)
}
}
class Middle extends Component{
render(){
return <Leaf/>
}
}
class App extends Component{
render(){
return (
<div>
+ <BatteryContext.Provider value={60}>
+ <Middle/>
+ </BatteryContext.Provider>
</div>
)
}
}
export default App;
运行结果
2、动态改变值
class App extends Component{
state = {
battery: 60
}
render(){
const { battery } = this.state
return (
<div>
+ <BatteryContext.Provider value={battery}>
+ <button
+ onClick={() => this.setState({battery: battery -1})}>Press</button>
+ <Middle/>
+ </BatteryContext.Provider>
</div>
)
}
}
动态改变值
3、多生产者多消费者的情况
import React, { Component,createContext } from 'react';
+const BatteryContext = createContext()
+const OnlineContext = createContext()
class Leaf extends Component{
render(){
return (
+ <BatteryContext.Consumer>
+ {
+ battery => (
+ <OnlineContext.Consumer>
+ {
+ online => <h1>Battery: {battery}, Online: {String(online)}</h1>
+ }
+ </OnlineContext.Consumer>
+ )
+ }
+ </BatteryContext.Consumer>
)
}
}
class Middle extends Component{
render(){
return <Leaf/>
}
}
class App extends Component{
state = {
battery: 60,
online: false
}
render(){
const { battery, online } = this.state
return (
<div>
+ <BatteryContext.Provider value={battery}>
+ <OnlineContext.Provider value={online}>
+ <button
+ onClick={() => this.setState({battery: battery -1})}>Press</button>
+ <button
+ onClick={() => this.setState({online: !online})}
+ >Switch</button>
+ <Middle/>
+ </OnlineContext.Provider>
+ </BatteryContext.Provider>
</div>
)
}
}
export default App;
如果组件找不到对应的值,并不会报错,而会启用Provider
的默认值。
import React, { Component,createContext } from 'react';
const BatteryContext = createContext(90)
class Leaf extends Component{
render(){
return (
<BatteryContext.Consumer>
{
battery => (
<h1>Battery: {battery}</h1>
)
}
</BatteryContext.Consumer>
)
}
}
class Middle extends Component{
render(){
return <Leaf/>
}
}
class App extends Component{
state = { }
render(){
const { battery } = this.state
return (
<div>
{/* <BatteryContext.Provider value={battery}> */}
<button
onClick={() => this.setState({battery: battery -1})}>Press</button>
<Middle/>
{/* </BatteryContext.Provider> */}
</div>
)
}
}
export default App;
(二)、ContextType
Context会让组件变得不纯粹,因为依赖了全局变量。所以这决定了Context一般不会大规模的使用。所以一般在一个组件中使用一个Context就好。
由于Consumer的特性,里面的代码必须是这个函数的返回值。这样就显得复杂与不优雅了。那该怎么解决呢?这样contextType就派上用场了。
首先我们用static来声明contextType:
static contextType = BatteryContext;
这样在运行时就可以获取到一个新的属性。我们来接收他。
const battery = this.context;
这样Consumer就可以完全不再使用了。
return<h1>Battery : {battery}</h1>
完整代码
import React, { Component,createContext } from 'react';
+const BatteryContext = createContext(90)
class Leaf extends Component{
+ static contextType = BatteryContext;
+ render(){
+ const battery = this.context
+ return (
+ <h1>Battery: {battery}</h1>
+ )
}
}
class Middle extends Component{
render(){
return <Leaf/>
}
}
class App extends Component{
state = { }
render(){
const { battery } = this.state
return (
<div>
{/* <BatteryContext.Provider value={battery}> */}
<button
onClick={() => this.setState({battery: battery -1})}>Press</button>
<Middle/>
{/* </BatteryContext.Provider> */}
</div>
)
}
}
export default App;
效果图
效果和使用Consumer没有什么区别。可见只有一个Context的时候,使用contextType要比使用Consumer简单的多。
二、Lazy、Suspen和ErrorBoundary
Lazy、Suspen用于解决运行时性能问题。
在平常的开发中,我们需要用到延迟加载的技术,比如懒加载,这样可以提升我们的性能。React项目中,我们可以通过下面两个步骤实现按需加载。
1、通过webpack的代码分割(Code Spliting )
2、import 异步导入组件
具体流程如下:
1、通过lazy异步导入组件
const About = lazy(() => import('./About.jsx'))
2、通过Suspense添加加载中状态,消除空档期错误
render(){
return (
<div>
<Suspense fallback={<div>loading</div>}>
<About></About>
</Suspense>
</div>
)
}
3、修改webpack异步导入组件数据,修改打包后文件名称
const About = lazy(() => import(/*webpackChunkName:"about"*/'./About.jsx'))
修改前
修改后
2、通过ErrorBoundary 捕获加载中错误
要捕获加载中错误,我们可以在生命周期钩子函数
componentDidCatch
进行错误处理
class App extends Component{
state = {
hasError : false
}
componentDidCatch(){
this.setState({
hasError: true
})
}
render(){
if(this.state.hasError){
return <div>Error</div>
}
return (
<div>
<Suspense fallback={<div>loading</div>}>
<About></About>
</Suspense>
</div>
)
}
}
也可以通过getDerivedStateFromError静态方法实现错误处理
static getDerivedStateFromError(){
return {
hasError: true
}
}
完整代码如下:
App.jsx
import React, { Component, lazy, Suspense } from 'react'
+const About = lazy(() => import(/*webpackChunkName:"about"*/'./About.jsx'))
class App extends Component{
+ state = {
+ hasError : false
+ }
+ static getDerivedStateFromError(){
+ return {
+ hasError: true
+ }
+ }
// componentDidCatch(){
// this.setState({
// hasError: true
// })
// }
render(){
+ if(this.state.hasError){
+ return <div>Error</div>
+ }
return (
<div>
<Suspense fallback={<div>loading</div>}>
<About></About>
</Suspense>
</div>
)
}
}
export default App
About.jsx
import React, { Component } from 'react'
class About extends Component{
render(){
return (<div>About</div>)
}
}
export default About
三、PureConponent、Memo实现指定组件进行渲染
当一个组件中包含子组件的时候,父组件数据的变化会导致子组件render函数的执行,如果父组件变化的数据子组件根本没有用到,那么子组件这样的更新显然是不合理的,怎么来解决这个问题了。
1、子组件使用shouldComponentUpdate
钩子函数实现
shouldComponentUpdate(nextProps, nextState){
if(nextProps.name === this.props.name){
return false
}
return true
}
完成代码
import React, { Component } from 'react'
class Foo extends Component{
+ shouldComponentUpdate(nextProps, nextState){
+ if(nextProps.name === this.props.name){
+ return false
+ }
+ return true
+ }
render(){
console.log('Foo header')
return (
<div>{this.props.name}-{this.props.count}</div>
)
}
}
class App extends Component{
state = {
count: 0,
name: 'antiai'
}
render(){
const { count, name } = this.state
return (
<div>
<button onClick={() => this.setState({ count : count + 1})}>add</button>
<Foo count={count} name={name}/>
</div>
)
}
}
export default App
不管怎么点击button
,Foo
组件中render函数就在最开始执行了一次,以后不再执行,因为button
点击事件并没有修改掉App
组件的name
属性。
2、子组件使用PureComponent
组件实现
1、PureComponent
对于简单的数据类型或者对象类型第一层的改变,执行结果跟shouldComponentUpdate
相同
import React, { Component, PureComponent } from 'react'
class Foo extends PureComponent{
render(){
console.log('Foo header')
return (
<div>{this.props.name}</div>
)
}
}
class App extends Component{
state = {
count: 0,
}
render(){
const {count} = this.state
return (
<div>
<button onClick={() => this.setState({ count : count + 1})}>add</button>
<Foo name='Mike' count={count}/>
</div>
)
}
}
export default App
此时要注意,子组件中的render函数的执行是根据父组件传入的值是否发生改变而执行的,pureComponent中,我们并不能像shouldComponentUpdate
中一样针对特定属性的更改来确定组件是否重新渲染。
2、PureComponent
对于多层数据结构的改变(对象)只会监测到第一层,也就是说第二层的改变不会影响到一层的改变。此处person中的age发生了改变,但是子组件接受的是person,所以没有监测到数据的变化。
import React, { Component, PureComponent } from 'react'
class Foo extends PureComponent{
render(){
console.log('Foo header')
return (
<div>{this.props.person.age}</div>
)
}
}
class App extends Component{
state = {
count: 0,
person: {
age: 1
}
}
render(){
const {count, person} = this.state
return (
<div>
<button onClick={() => {
person.age ++
this.setState({
count: count + 1
})
}}>
Add</button>
<Foo person={person}/>
</div>
)
}
}
export default App
要解决上面的问题有下面两种用法
方法一:给Foo
组件传递回调函数cb
import React, { Component, PureComponent } from 'react'
class Foo extends PureComponent{
render(){
console.log('Foo header')
return (
<div>{this.props.person.age}</div>
)
}
}
class App extends Component{
state = {
count: 0,
person: {
age: 1
}
}
render(){
const { person} = this.state
return (
<div>
<button onClick={() => {
person.age ++
this.setState({
person
})
}}>
Add</button>
<Foo person={person} cb={() => {}}/>
</div>
)
}
}
export default App
在上面,我们给Foo
组件额外传递了一个cb
函数,以为每次函数都是新的,所以,每次都会触发子组件更新。
方法二:使用memo组件
import React, { Component, memo } from 'react'
const Foo = memo(function Foo(props){
console.log('Foo render')
return <div>{props.person.age}</div>
})
class App extends Component{
state = {
count: 0,
person: {
age: 1
}
}
callback = () => { }
render(){
const { person} = this.state
return (
<div>
<button onClick={() => {
person.age ++
this.setState({
person
})
}}>
Add</button>
<Foo person={person} cb={() => {}}/>
</div>
)
}
}
export default App
React.memo()
是一个高阶函数,它与 React.PureComponent
类似,但是一个函数组件而非一个类。
总结:
1、pureComponent 提供简单的对比算法,避免组件重新渲染,减少性能开销
2、无状态组件是函数式的,不能继承PureComponent
,可以使用memo
达到相同的效果
推论
拆分越细的组件,传入属性越简单,使用PureComponent
和memo
的机会越多。
网友评论