17、Reac系列之--無狀態組件你真的知道嗎?

stateLessbg.png

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

PS:轉載請注明出處
作者:TigerChain
地址:http://www.lxweimin.com/p/980abadd8a18
本文出自 TigerChain簡書

React 教程系列

教程簡介

  • 1、閱讀對象

本篇教程適合初學者,老鳥直接略過,如果有誤,歡迎指出,謝謝。

  • 2、教程難度

初級

一、復習

前面我們說過在 React 中編寫組件有三種方式:我們再來復習一下

  • ES5 寫法
  • 有狀態組件「stateful components」
  • 無狀態組件「stateless components」

ES5 編寫組件

# es5
var React = require('react');
var ReactDOM = require('react-dom');

var CusImg = React.createClass({
    
  render: function() {
   
    return (
      <div className={this.props.style}>
        <img src={this.props.imgurl}/>
        <text className={this.props.textStyle}>{this.props.text}</text>
        </div>
    );
  }
});

ReactDOM.render(
  <CusImg />,
  document.getElementById('container')
);

ES6 編寫有狀態組件


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

  render() {
    return (
      <div className={this.props.style}>
        <img src={this.props.imgurl}/>
        <text className={this.props.textStyle}>{this.props.text}</text>
    </div>);
  }
}

CusImg.propTypes = {
};

無狀態組件

無狀態組件顧名思義就是沒有狀態的組件,如果一個組件不需要管理 state 只是純的展示,那么就可以定義成無狀態組件。無狀態組件是在 React 在 v0.14 之后推出的

PS:順便說一下,無狀態組件是沒有 refs 屬性的

# 無狀態組件
const Component (props)=>(
    <div>
        {props.text}
        ...
    </div>
)

二、實例來修改無狀態組件

來個非常簡單的

需求:定義一個帶文字的圖片組件「上面是圖片,下面是文字」。

先看看最終效果:

qcode.png

1、核心代碼:自定義一個有狀態的 CusImg 的組件

import React, { Component, PropTypes } from 'react';

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

  render() {
    return (
      <div className={this.props.style}>
        <img src={this.props.imgurl}/>
        <text className={this.props.textStyle}>{this.props.text}</text>
    </div>);
  }
}

CusImg.propTypes = {
};

由于此組件只關心數據傳遞「props」,和狀態無關,我們可以定義為無狀態組件如下:

2、修改為無狀態組件

const CusImg = (props)=>(
  <div className={props.style}>
    <img src={props.imgurl}/>
    <text className={props.textStyle}>{props.text}</text>
</div>
);

module.exports = CusImg

這樣我們完成了一個無狀態組件

三、無狀態組件優勢

1、無狀態組件優勢

既然有無狀態組件「存在就有必要」,那么無狀態組件有什么優點呢?

  • 1、代碼整潔、可讀性高

如下兩幅圖,分別是有普通組件和無狀態組件,對比可以看出無狀態組件的簡潔性

stateful.png
stateless.png
  • 2、沒有 this「由于使用的是箭頭函數事件無需綁定」
  • 3、便于測試
  • 4、沒有生命周期的方法和狀態只是一個方法所以性能高?

對于第 4 個我打一個 ? 號,按理說應該是這樣的,無狀態函數沒有看見 render 等方法,應該性能比有狀態的性能高,而且好多文章都是這樣說的。所以我決定試一試。

2、無狀態組件的性能

現在我們只是直觀的感受無狀態組件的性能比有狀態的組件的性能高,目前都是 YY 出來的,如何能正確的解答我們心中的疑惑呢?那就是實驗,沒錯,我們寫一個 Demo 來驗證一下

在編寫 demo 之前,我們先看一個小例子,來感受一下無狀態組件最終長什么樣子.

  • 1、 我們先把上面圖片文字調用一下

編寫 app 組件,然后調用

# app.js
import React, { Component, PropTypes } from 'react';

import styles from '../css/app.css'
import CusImg from './cusimg'

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

  }

  render() {
    return (
      <div className={styles.haha}>
        <CusImg
          style={styles.root}
          textStyle={styles.textStyle}
          imgurl={require('../imgs/qcord.png')}
          text="二維碼" />

      </div>
  );
  }
}
  • 2、使用 webpack 打包成 bundle.js 文件來查看最終生成的 React 方法
webpack ./app/app.js ./public/bundle.js

以上命令是會對我的項目的,這句命令就把 app.js 打到 bundle.js 中了

我們來看看 app 組件在 bundle.js 中對應 React 的方法,如下圖:

createEle.png

從綠色線框我們可以看到,react 調用 createElement 方法生成了一個 CusImg 組件

  • 3、修改 app.js 中調用無狀態組件的方式,只修改 render 方法
# app.js

render() {
    return (
      <div className={styles.haha}>
         {CusImg({
           style:styles.root ,
           textStyle:styles.textStyle,
           imgurl:require('../imgs/qcord.png'),
           text:"二維碼"
         })}
      </div>
  );
  }

然后繼續執行 webpack ./app/app.js ./public/bundle.js 命令,此時觀察 bundle.js 中 app 組件對應的 React 源碼

callstateless.png

和 2 步驟中的圖片對比,發現出區別來了沒,竟然沒有 _react2.default.createElement(_cusimg2.default, {...}) 取而代之的是一個方法,這到底有神馬鳥用?我告訴你使用 3 中的寫法才是發揮了無狀態組件真正的作用「達到優化性能的目的」,接著往下看

四、組件的性能對比

組件繼承 Component

廢話不多說,直接上代碼對比有狀態組件,無狀態組件兩種調用試下的性能,接著上面的代碼繼續編寫

  • 1、修改 index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
<!-- 添加以下三個 div -->
  <div id="stateless-functional-direct-call"></div>
  <div id="stateless-functional-mounted"></div>
  <div id="stateful"></div>
<div id="container"></div>
<script src="bundle.js"></script>
</body>
</html>

我們給 index.html 中添加了三個 div

  • 2、修改 main .js
import React from "react";
import {render} from 'react-dom';

const Dot = (props) =>(<span>.</span>);

class DotComponent extends React.PureComponent {
  render() {
    return <span>.</span>;
  }
}

class Main extends React.PureComponent {
  render() {
    var dots = Array(500).fill(0).map(x => {
      if(this.props.kind == 'stateless-functional-direct-call') {
        return Dot();
      } else if(this.props.kind == 'stateless-functional-mounted') {
        return React.createElement(Dot, {}, {});
      } else if(this.props.kind == 'stateful') {
        return React.createElement(DotComponent, {}, {});
      }
    })
    return React.createElement('div', {}, ...dots);
  }
}

let prevBenchmarkTime, benchmarkCount, statefulTotalTime, statelessFunctionalMountedTotalTime, statelessFunctionalDirectCallTotalTime;
benchmarkCount = 0;
//狀態組件花費時間
statefulTotalTime = 0;
//無狀態組件掛載花費時間
statelessFunctionalMountedTotalTime = 0;
//直接調用無狀態組件花費時間
statelessFunctionalDirectCallTotalTime = 0;

const run = () => {
  ['stateful', 'stateless-functional-mounted', 'stateless-functional-direct-call'].forEach(kind => {
    const prevTime = performance.now();

    var items = [];
    var i, len;
    for (i = 0, len = 20; i < len; i++) {
      items.push(i);
    }
    items.forEach(i => {
      render((
        <Main kind={kind}/>
        ), document.getElementById(kind));
    });

    const time = Math.round(performance.now() - prevTime);

    if(kind == 'stateless-functional-direct-call') {
      statelessFunctionalDirectCallTotalTime = statelessFunctionalDirectCallTotalTime + time
    } else if(kind == 'stateless-functional-mounted') {
      statelessFunctionalMountedTotalTime = statelessFunctionalMountedTotalTime + time
    } else if(kind == 'stateful') {
      statefulTotalTime = statefulTotalTime + time
    }

    const perf = (Math.round((1-time/prevBenchmarkTime)*100) || '  ')
    prevBenchmarkTime = time;
  })
  prevBenchmarkTime = undefined
  benchmarkCount = benchmarkCount + 1
  console.log('.')
  return
}

const start = ((count=10) => {
  console.log(`Running %c${count} %ctimes ...`, 'font-weight: bold', 'font-weight: normal');
  Array(count).fill(0).forEach(x => run())
  console.log(`Stateful                         took ${statefulTotalTime}ms`);
  console.log(`Stateless Functional Mounted     took ${statelessFunctionalMountedTotalTime}ms %c${Math.round((1-statelessFunctionalMountedTotalTime/statefulTotalTime)*100)}% %c`, 'color:green', 'color:black');
  console.log(`Stateless Functional Direct Call took ${statelessFunctionalDirectCallTotalTime}ms %c${Math.round((1-statelessFunctionalDirectCallTotalTime/statefulTotalTime)*100)}% %c`, 'color:green', 'color:black');
  console.log(`%c`, 'font-size: 100px')
})()
  • 3、運行查看結果
yarn start
per.gif

從上圖可以知道,無狀態組件性能確實比有狀態的性能高,但是只是提高了 6% 左右,但是按照我們上面 3 的方式調用無狀態組件性能提高了 60% 左右

PS:無狀態組件本身性能提高不是很明顯,完全取決于調用方式,如果按照傳統的組件調用無狀態組件那么性能提高微乎其微,如果按照方法的形式調用無狀態組件,那么性能提高不少。「建議大家以后調用無狀態組件采用性能高的方式」

  • 4、得出結論

從以上各種圖可知,無狀態組件的調用如果按常規的方式調用,還是會創建元素,掛載,所以直接調用無狀態組件性能最高

組件繼承 PrueComponent

  • 1、我們直接修改兩個地方即可,main.js
modifyprue.png

紅色標注就是我們修改的地方

  • 2、運行查看結果
yarn start
purecom.png

我們看消耗時間簡直直接繼承 Component 組件的 25倍之一左右,這是為什么呢?

  • 3、PureComponent

PureComponent 是 React 15.3在2016.06.29發布了,它取代了之前的 pure-render-mixin「最小渲染」。

PureComponent 的作用:用來提升性能,因為它減少了應用中的渲染次數。

PureComponent 改變了生命周期方法 shouldComponentUpdate,并且它會自動檢查組件是否需要重新渲染。這時,只有 PureComponent 檢測到 state 或者 props 發生變化時,PureComponent 才會調用 render方法,因此,你不用手動寫額外的檢查,就可以在很多組件中改變state, 例如:

shouldComponentUpdate(nextProps, nextState) {
  return nextProps.user.id === props.user.id;
}

具體請看源文:https://60devs.com/pure-component-in-react.html

好了對于無狀態組件就到此結束了,相信大家對無狀態組件有了一個全新的認識

最近開了公號,以后文章內容第一時間會發在公號中,希望大家關注,掃描以下二維碼即可關注

據說每一個勤奮努力想成為非常牛 B 的人都會點個喜歡或轉發的

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,287評論 25 708
  • 原教程內容詳見精益 React 學習指南,這只是我在學習過程中的一些閱讀筆記,個人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,860評論 1 18
  • 1 熱發布 網頁發布 VS APP發布 網頁發布:服務端上線新的網頁代碼,用戶端通過鏈接直接訪問。 APP發布:?...
    kkmoving閱讀 4,148評論 1 15
  • 接了現在的爛攤子每天內心都無法平靜雖然到處都有正能量人物到處都可以喝到美味雞湯不過雞湯不能當飯吃如果一件事情要靠毅...
    承謙閱讀 805評論 0 0
  • 馬路邊的景觀花 你見過他們嗎 現在又是哪一株啊 二月藍又或是雞冠花 是否還不死心想掙扎著扎根住下 連土地貧瘠也不怕...
    艾素晴雯閱讀 265評論 0 2