React新特性走一波

一、Context与ContextType

       Context与ContextType用于解决编程开发效率问题。

(一)、Context

1、定义

       Context提供一种方式,能够让数据在组件树中传递而不必一级一级手动传递。

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达到相同的效果

推论
       拆分越细的组件,传入属性越简单,使用PureComponentmemo的机会越多。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352