Loading

♠ react脚手架、组件化开发、render更新流程

React脚手架

React脚手架本身需要依赖node,所以我们需要安装node环境。

什么是npm?

  • 全称 Node Package Manager,即“node包管理器”;
  • 作用肯定是帮助我们管理一下依赖的工具包(比如react、react-dom、axios、babel、webpack等等);
  • 开发的目的就是为了解决“模块管理很糟糕”的问题;

另外,还有一个大名鼎鼎的node包管理工具yarn:

  • Yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具;
  • Yarn 是为了弥补 npm 的一些缺陷而出现的;
  • 早期的npm存在很多的缺陷,比如安装依赖速度很慢、版本依赖混乱等等一系列的问题;
  • 虽然从npm5版本开始,进行了很多的升级和改进,但是依然很多人喜欢使用yarn;

React脚手架默认也是使用yarn;

补充:在国内,某些情况使用npm和yarn可能无法正常安装一个库,这个时候我们可以选择使用cnpm

  • npm install -g cnpm --registry=https://registry.npm.taobao.org

最后一个需要安装的是创建React项目的脚手架:npm install -g create-react-app

目录结构分析

整个目录结构都非常好理解,只是有一个PWA相关的概念:

  • PWA全称Progressive Web App,即渐进式WEB应用;
  • 一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用;
  • 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能;
  • 这种Web存在的形式,我们也称之为是 Web App;

PWA解决了哪些问题呢?

  • 可以添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏;
  • 实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能;
  • 实现了消息推送;
  • 等等一系列类似于Native App相关的功能;

React脚手架讲webpack相关的配置隐藏起来了(其实从Vue CLI3开始,也是进行了隐藏),我们可以执行一个package.json文件中的一个脚本:"eject": "react-scripts eject"。

我们先将不需要的文件统统删掉:

  • 1.将src下的所有文件都删除
  • 2.将public文件下出列favicon.ico和index.html之外的文件都删除掉

在src目录下,创建一个index.js文件,因为这是webpack打包的入口。

在index.js中开始编写React代码:

在模块化开发中,我们需要手动的来导入React、ReactDOM,因为它们都是在我们安装的模块中;

如果我们不希望直接在 ReactDOM.render 中编写过多的代码,就可以单独抽取一个组件App.js

什么是组件化开发呢?

组件化也是类似的思想:

  • 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
  • 但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

我们需要通过组件化的思想来思考整个应用程序:

  • 我们将一个完整的页面分成很多个组件;
  • 每个组件都用于实现页面的一个功能块;
  • 而每一个组件又可以进行细分;
  • 而组件本身又可以在多个地方进行复用;

React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:

  • 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
  • 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component);
  • 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container Component);

这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:

  • 函数组件、无状态组件、展示型组件主要关注UI的展示;
  • 类组件、有状态组件、容器型组件主要关注数据逻辑;

当然还有很多组件的其他概念:比如异步组件、高阶组件等

类组件

类组件的定义有如下要求:

  • 组件的名称是大写字符开头(无论类组件还是函数组件)
  • 类组件需要继承自 React.Component
  • 类组件必须实现render函数

使用class定义一个组件:

  • constructor是可选的,我们通常在constructor中初始化一些数据;
  • this.state中维护的就是我们组件内部的数据;
  • render() 方法是 class 组件中唯一必须实现的方法;

当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:

  • React 元素
    • 通常通过 JSX 创建。
    • 例如,<div /> 会被 React 渲染为 DOM 节点,<MyComponent /> 会被 React 渲染为自定义组件;
    • 无论是 <div /> 还是 <MyComponent /> 均为 React 元素。
  • 数组或 fragments:使得 render 方法可以返回多个元素。
  • Portals:可以渲染子节点到不同的 DOM 子树中。
  • 字符串或数值类型:它们在 DOM 中会被渲染为文本节点
  • 布尔类型或 null:什么都不渲染

函数组件

函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容。

函数组件有自己的特点(当然,还有hooks):

没有生命周期,也会被更新并挂载,但是没有生命周期函数;

没有this(组件实例);

没有内部状态(state);

查看代码
import React, { Component } from 'react';
//如果是export导出,需要{App}这样import
// export default class App extends Component {
//   constructor() {
//     super();

//     this.state = {
//       message: "你好啊"
//     }
//   }

//   render() {
//     return (
//       <div>
//         <span>我是App组件</span>
//         {/* alt + shift + f: 对代码进行格式化 */}
//         <h2>{this.state.message}</h2>
//       </div>
//     )
//   }
// }

/**
 * 函数式组件的特点:
 *  1.没有this对象
 *  2.没有内部的状态
 */
export default function App() {
  return (
    <div>
      <span>我是function的组件: App组件</span>
      <h2>counter</h2>
      <button>+1</button>
      <h2>你好啊,王小波</h2>
    </div>
  )
}

认识生命周期

很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期

生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段;

  • 比如装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
  • 比如更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 比如卸载过程(Unmount),组件从DOM树中被移除的过程;

React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数就是生命周期函数:

  • 比如实现componentDidMount函数:组件已经挂载到DOM上时,就会回调;
  • 比如实现componentDidUpdate函数:组件已经发生了更新时,就会回调;
  • 比如实现componentWillUnmount函数:组件即将被移除时,就会回调;
  • 我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;

我们谈React生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的;(可以通过hooks来模拟一些生命周期的回调)

Constructor

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。constructor中通常只做两件事情:

  • 通过给 this.state 赋值对象来初始化内部的state;
  • 为事件绑定实例(this);

componentDidMount

componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。componentDidMount中通常进行哪里操作呢?

  • 依赖于DOM的操作可以在这里进行;
  • 在此处发送网络请求就最好的地方;(官方建议)
  • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);

componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作;例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等;

不常用生命周期函数

除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数:

  • getDerivedStateFromProps:state 的值在任何时候都依赖于 props时使用;该方法返回一个对象来更新state;
  • getSnapshotBeforeUpdate:在React更新DOM之前回调的一个函数,可以获取DOM更新前的一些信息(比如说滚动位置);
  • shouldComponentUpdate:该生命周期函数很常用,待讲性能优化时再来详细讲解;

另外,React中还提供了一些过期的生命周期函数,这些函数已经不推荐使用。更详细的生命周期相关的内容,可以参考官网。

查看代码
import React, { Component } from 'react';

class Cpn extends Component {
  render() {
    return <h2>我是Cpn组件</h2>
  }

  componentWillUnmount() {
    console.log("调用了Cpn的componentWillUnmount方法");
  }
}

export default class App extends Component {

  constructor() {
    super();

    this.state = {
      counter: 0,
      isShow: true
    }

    console.log("执行了组件的constructor方法");
  }

  render() {
    console.log("执行了组件的render方法");

    return (
      <div>
        我是App组件
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <hr/>
        <button onClick={e => this.changeCpnShow()}>切换</button>
        {this.state.isShow && <Cpn/>}
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  changeCpnShow() {
    this.setState({
      isShow: !this.state.isShow
    })
  }

  componentDidMount() {
    console.log("执行了组件的componentDidMount方法");
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("执行了组件的componentDidUpdate方法");
  }
}

认识组件间的通信

在开发过程中,我们会经常遇到需要组件之间相互进行通信:

  • p 比如App可能使用了多个Header,每个地方的Header展示的内容不同,那么我们就需要使用者传递给Header一些数据,让其进行展示;
  • p 又比如我们在Main中一次性请求了Banner数据和ProductList数据,那么就需要传递给他们来进行展示;
  • p 也可能是子组件中发生了事件,需要由父组件来完成某些操作,那就需要子组件向父组件传递事件;

父组件在展示子组件,可能会传递一些数据给子组件:

  • 父组件通过 属性=值 的形式来传递给子组件数据;
  • 子组件通过 props 参数获取父组件传递过来的数据;

父组件传递子组件 - 类组件和函数组件

查看代码
import React, { Component } from 'react';


class ChildCpn extends Component {
  //不用super(props),在render中也可以使用this.props,react源码中对组件的props进行了保存
  constructor() {
    super();
  }

  componentWillMount() {

  }

  componentDidMount() {
    console.log(this.props, "componentDidMount");
  }

  render() {
    // console.log(this.props, "render");
    const { name, age, height } = this.props;
    return (
      <h2>子组件展示数据: {name + " " + age + " " + height}</h2>
    )
  }
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="why" age="18" height="1.88" />
        <ChildCpn name="kobe" age="40" height="1.98" />
      </div>
    )
  }
}
查看代码
import React, { Component } from 'react';

function ChildCpn(props) {
  const { name, age, height } = props;

  return (
    <h2>{name + age + height}</h2>
  )
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="why" age="18" height="1.88" />
        <ChildCpn name="kobe" age="40" height="1.98" />
      </div>
    )
  }
}

参数propTypes

对于传递给子组件的数据,有时候我们可能希望进行验证,特别是对于大型项目来说:

  • 当然,如果你项目中默认继承了Flow或者TypeScript,那么直接就可以进行类型验证;
  • 但是,即使我们没有使用Flow或者TypeScript,也可以通过 prop-types 库来进行参数验证;

React v15.5 开始,React.PropTypes 已移入另一个包中:prop-types 库

  • 比如验证数组,并且数组中包含哪些元素;
  • 比如验证对象,并且对象中包含哪些key以及value是什么类型;
  • 比如某个原生是必须的,使用 requiredFunc: PropTypes.func.isRequired

如果没有传递,我们希望有默认值呢?  我们使用defaultProps就可以了

查看代码
import React, { Component } from 'react';

import PropTypes from 'prop-types';

function ChildCpn(props) {
  const { name, age, height } = props;
  console.log(name, age, height);
  const { names } = props;

  return (
    <div>
      <h2>{name + age + height}</h2>
      <ul>
        {
          names.map((item, index) => {
            return <li>{item}</li>
          })
        }
      </ul>
    </div>
  )
}

class ChildCpn2 extends Component {
  // es6中的class fields写法
  static propTypes = {

  }

  static defaultProps = {

  }
}

ChildCpn.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  height: PropTypes.number,
  names: PropTypes.array
}

ChildCpn.defaultProps = {
  name: "why",
  age: 30,
  height: 1.98,
  names: ["aaa", "bbb"]
}

export default class App extends Component {
  render() {
    return (
      <div>
        <ChildCpn name="why" age={18} height={1.88} names={["abc", "cba"]}/>
        <ChildCpn name="kobe" age={40} height={1.98} names={["nba", "mba"]}/>
        <ChildCpn/>
      </div>
    )
  }
}

子组件传递父组件

某些情况,我们也需要子组件向父组件传递消息:

  • 在vue中是通过自定义事件来完成的;
  • 在React中同样是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可;
查看代码
import React, { Component } from 'react';


class CounterButton extends Component {
  render() {
    const {onClick} = this.props;
    return <button onClick={onClick}>+1</button>
  }
}


export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+</button>
        <CounterButton onClick={e => this.increment()} name="why"/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

Context应用场景

如果层级更多的话,一层层传递是非常麻烦,并且代码是非常冗余的:

  • React提供了一个API:Context;
  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
  • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

React.createContext

  • 创建一个需要共享的Context对象:
  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值

Context.Provider

  • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
  • Provider 接收一个 value 属性,传递给消费组件;
  • 一个 Provider 可以和多个消费组件有对应关系;
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
  • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;

Class.contextType

  • 挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
  • 这能让你使用 this.context 来消费最近 Context 上的那个值;
  • 你可以在任何生命周期中访问到它,包括 render 函数中;

Context.Consumer

  • 这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
  • 这里需要 函数作为子元素(function as child)这种做法;
  • 这个函数接收当前的 context 值,返回一个 React 节点;

Context的基本使用

  • 什么时候使用默认值defaultValue呢?
  • 什么时候使用Context.Consumer呢?
  • 1.当使用value的组件是一个函数式组件时;
  • 2.当组件中需要使用多个Context时;
查看代码
import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

class ProfileHeader extends Component {
  render() {
    console.log(this.context);
    // jsx -> 
    return (
      <div>
        <h2>用户昵称: {this.context.nickname}</h2>
        <h2>用户等级: {this.context.level}</h2>
      </div>
    )
  }
}

ProfileHeader.contextType = UserContext;
ProfileHeader.contextType = ThemeContext;

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
        </UserContext.Provider>
        <Profile />
      </div>
    )
  }
}
查看代码
import React, { Component } from 'react';

// 创建Context对象
const UserContext = React.createContext({
  nickname: "aaaa",
  level: -1
})

function ProfileHeader() {
  return (
    <UserContext.Consumer>
      {
        value => {
          return (
            <div>
              <h2>用户昵称: {value.nickname}</h2>
              <h2>用户等级: {value.level}</h2>
            </div>
          )
        }
      }
    </UserContext.Consumer>
  )
}

function Profile(props) {
  return (
    <div>
      <ProfileHeader />
      <ul>
        <li>设置1</li>
        <li>设置2</li>
        <li>设置3</li>
        <li>设置4</li>
      </ul>
    </div>
  )
}

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      nickname: "kobe",
      level: 99
    }
  }

  render() {
    return (
      <div>
        <UserContext.Provider value={this.state}>
          <Profile />
        </UserContext.Provider>
      </div>
    )
  }
}

为什么使用setState

异步和同步

开发中我们并不能直接通过修改state的值来让界面发生更新:

  • 因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是这种方式的修改React并不知道数据发生了变化;
  • React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化;
  • 我们必须通过setState来告知React数据已经发生了变化;

setState方法是从Component中继承过来的。

setState的更新是异步的?

  • 最终打印结果是Hello World;
  • 可见setState是异步的操作,我们并不能在执行完setState之后立马拿到最新的state的结果

为什么setState设计为异步呢?

  •  setState设计为异步其实之前在GitHub上也有很多的讨论;
  • React核心成员(Redux的作者)Dan Abramov也有对应的回复,有兴趣的同学可以参考一下;https://github.com/facebook/react/issues/11527#issuecomment-360199710;

setState设计为异步,可以显著的提升性能;

  • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
  • 最好的办法应该是获取到多个更新,之后进行批量更新;

如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步;state和props不能保持一致性,会在开发中产生很多的问题;

那么如何可以获取到更新后的值呢?

方式一:setState的回调

  • setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行;格式如下:setState(partialState, callback)

方式二:我们也可以在生命周期函数

setState一定是异步吗?

验证一:在setTimeout中的更新:

验证二:原生DOM事件:

其实分成两种情况:

  • 在组件生命周期或React合成事件中,setState是异步;
  • 在setTimeout或者原生dom事件中,setState是同步;

数据的合并

查看代码
import React, { Component } from 'react'

export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
      </div>
    )
  }

  increment() {
    // 1.setState本身被合并
    // this.setState({
    //   counter: this.state.counter + 1
    // });
    // this.setState({
    //   counter: this.state.counter + 1
    // });
    // this.setState({
    //   counter: this.state.counter + 1
    // });

    // 2.setState合并时进行累加
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    });
  }
}

 

React的更新流程

React的更新

React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。

React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI:

  • 如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素的数https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf;
  • 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围;
  • 这个开销太过昂贵了,React的更新性能会变得非常低效;

于是,React对这个算法进行了优化,将其优化成了O(n),如何优化的呢?

  • 同层节点之间相互比较,不会垮节点比较;
  • 不同类型的节点,产生不同的树结构;
  • 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;

情况一:对比不同类型的元素

当节点为不同的元素,React会拆卸原有的树,并且建立起新的树:

  • 当一个元素从 <a> 变成 <img>,从 <Article> 变成 <Comment>,或从 <Button> 变成 <div> 都会触发一个完整的重建流程;
  • 当卸载一棵树时,对应的DOM节点也会被销毁,组件实例将执行 componentWillUnmount() 方法;
  • 当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中,组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法;

比如下面的代码更改:

React 会销毁 Counter 组件并且重新装载一个新的组件,而不会对Counter进行复用

情况二:对比同一类型的元素

当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。

比如下面的代码更改:通过比对这两个元素,React 知道只需要修改 DOM 元素上的 className 属性;

情况三:对子节点进行递归

在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个mutation。

我们来看一下在最后插入一条数据的情况:

  • 前面两个比较是完全相同的,所以不会产生mutation;
  • 最后一个比较,产生一个mutation,将其插入到新的DOM树中即可;

但是如果我们是在中间插入一条数据:

  • React会对每一个子元素产生一个mutation,而不是保持 <li>星际穿越</li>和<li>盗梦空间</li>的不变;
  • 这种低效的比较方式会带来一定的性能问题;

keys的优化

我们在前面遍历列表时,总是会提示一个警告,让我们加入一个key属性:

方式一:在最后位置插入数据

  • 这种情况,有无key意义并不大

方式二:在前面插入数据

  • 这种做法,在没有key的情况下,所有的li都需要进行修改;

key的注意事项:

  • key应该是唯一的;
  • key不要使用随机数(随机数在下一次render时,会重新生成一个数字);
  • 使用index作为key,对性能是没有优化的;

render函数被调用

而当App的render函数被调用时,所有的子组件的render函数都会被重新调用;那么,我们可以思考一下,在以后的开发中,我们只要是修改了App中的数据,所有的组件都需要重新render,进行diff算法,性能必然是很低的

  • 事实上,很多的组件没有必须要重新render;
  • 它们调用render应该有一个前提,就是依赖的数据(state、props)发生改变时,再调用自己的render方法;

如何来控制render方法是否被调用呢?通过shouldComponentUpdate方法即可;

shouldComponentUpdate该方法有两个参数:

  • 参数一:nextProps 修改之后,最新的props属性
  • 参数二:nextState 修改之后,最新的state属性

该方法返回值是一个boolean类型

  • 返回值为true,那么就需要调用render方法;
  • 返回值为false,那么久不需要调用render方法;
  • 默认返回的是true,也就是只要state发生改变,就会调用render方法;

比如我们在App中增加一个message属性:

jsx中并没有依赖这个message,那么它的改变不应该引起重新渲染;但是因为render监听到state的改变,就会重新render,所以最后render方法还是被重新调用了;

PureComponent

如果所有的类,我们都需要手动来实现 shouldComponentUpdate,那么会给我们开发者增加非常多的工作量。

将class继承自PureComponent

高阶组件memo

查看代码

import React, { PureComponent, memo } from 'react';

// Header
const MemoHeader = memo(function Header() {
  console.log("Header被调用");
  return <h2>我是Header组件</h2>
})


// Main
class Banner extends PureComponent {
  render() {
    console.log("Banner render函数被调用");
    return <h3>我是Banner组件</h3>
  }
}

const MemoProductList = memo(function ProductList() {
  console.log("ProductList被调用");
  return (
    <ul>
      <li>商品列表1</li>
      <li>商品列表2</li>
      <li>商品列表3</li>
      <li>商品列表4</li>
      <li>商品列表5</li>
    </ul>
  )
})

class Main extends PureComponent {
  render() {
    console.log("Main render函数被调用");
    return (
      <div>
        <Banner/>
        <MemoProductList/>
      </div>
    )
  }
}

// Footer
const MemoFooter = memo(function Footer() {
  console.log("Footer被调用");
  return <h2>我是Footer组件</h2>
})


export default class App extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      counter: 0
    }
  }

  render() {
    console.log("App render函数被调用");
    return (
      <div>
        <h2>当前计数: {this.state.counter}</h2>
        <button onClick={e => this.increment()}>+1</button>
        <MemoHeader/>
        <Main/>
        <MemoFooter/>
      </div>
    )
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}
posted @ 2022-01-28 19:07  sunflower-js  阅读(146)  评论(0编辑  收藏  举报