从入行前端起,我就开始使用react开发,直至今年,因为新公司使用vue,所以我也切换到vue开发。
开发过程中做了一个需求:实现几百个轮播,并且可以切换不同个数的轮播。
最终这个项目我使用了vue+swiper,期间遇到了比较严重的卡顿,也就是性能问题,后面我也做了一些优化。
由此,我很好奇使用react和vue分别实现swiper在性能方面有什么不同。
为了避开开发环境的影响,测试的数据分别在打包后的 生产状态下进行统计!
1、使用create-react-app创建一个react项目,使用vue-cli创建一个vue项目。2个项目我都没有加vuex或redux,仅仅是一个简单的demo,确保受其他因素的影响小一些。
2、跳过添加代码的步骤,我们看一下测试的demo图。
3、生成测试数据
new Array(1000).fill({a: 1000})
第一回合 比较1000个轮播的性能
初始化的时候,2个项目都是1000个轮播,我们对比一下2者目前状态下的性能。
React:
Vue:
有2个指标需要关注一下,Scripting和Rendering,渲染方面,2者时间差不多,但js执行上,React不如Vue快。
接着,对比一下内存占用情况。
React:
Vue:
React内存占用高一点,Vue相对低一些。
总体来说,该回合React完败。
第二回合 比较10000个轮播性能
初始化的时候,2个项目都是10000个轮播,我们对比一下2者目前状态下的性能。
这一次,基数暴增!!!
React:
Vue:
渲染方面和js执行上,React都比Vue慢。
接着,对比一下内存占用情况。
React:
Vue:
React内存比Vue高。
总体来说,该回合React完败。
第三回合 比较切换不同轮播个数时候的性能变化
之前2个回合React都输了,我不服!!来个究极对比,this.setState和this.data更新那个更快??
React:初始化的时候是1000,然后我点击按钮切换到2000, 5000,10000,100,监测性能变动。
Vue:初始化的时候是1000,然后我点击按钮切换到2000, 5000,10000,100,监测性能变动。
结果分析:在切换过程中,2者的内存都是在增长,整体js的执行时间,React比Vue长一点,在点击切换的时候,我也能感受到React卡顿比较明显。在切换结束后,双方都变成了100个轮播的状态,而此时React的内存还是很高,Vue已经降到很低了。
结论
我还做了其他个数的性能比较,发现React始终不如Vue,数据量越大,React就越卡,而Vue的体验上始终比较流畅。
当然,这个结果不能作为最终的结论,因为更新数据的时候,该数据的对象层次越深,在虚拟DOM的js执行上就可能影响不同。
我自己做的那个需求里面,就是一个几十KB的JavaScript对象,在Vue中进行Swiper渲染和切换,最后的效果也是很卡。
优化方案
那么既然React和Vue在渲染数据量比较大的Swiper上都表现的不那么好,有没有什么优化方案?
答案是有的!!
Swiper提供了一种 “virtual slide”的渲染方式,这种渲染可以100%避免性能瓶颈,但是也有缺点,就是无法做到一些特殊的交互效果,所以最终我没有采用这种方案。
最后附上核心源码:
React:App.js文件
import React, { Component } from 'react'
import './App.css'
import 'swiper/dist/css/swiper.min.css'
import Swiper from 'swiper'
import logo from './logo.svg'
class App extends Component {
state = {
arrList: new Array(1000).fill({a: 1000}),
options: {
spaceBetween: 10,
loop:true,
freeMode: true,
loopedSlides: 5, //looped slides should be the same
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
}
}
initSwiper = () => {
this.swiper = new Swiper('.gallery-top', this.state.options)
}
componentDidMount() {
this.initSwiper()
}
componentWillUpdate(nextProps, nextState, nextContext) {
this.start = new Date().getTime()
if (this.swiper) {
this.swiper.destroy()
}
}
componentDidUpdate(prevProps, prevState, snapshot) {
this.initSwiper()
this.end = new Date().getTime()
console.log('time: ', this.end - this.start)
}
switchData = (val) => {
this.setState(() => ({arrList: new Array(val).fill({a: val})}))
}
render() {
const { arrList } = this.state
return (
<div className="App">
<div className="switch-data">
<span onClick={() => this.switchData(10000)}>10000</span>
<span onClick={() => this.switchData(5000)}>5000</span>
<span onClick={() => this.switchData(2000)}>2000</span>
<span onClick={() => this.switchData(1000)}>1000</span>
<span onClick={() => this.switchData(100)}>100</span>
</div>
<div className="swiper-container gallery-top">
<div className="swiper-wrapper">
{
arrList.length > 0 && arrList.map((item, key) => {
return (
<div key={key} className="swiper-slide">
<p>key: {key}</p>
<p>value: {item.a}</p>
<img src={logo} alt=""/>
</div>
)
})
}
</div>
<div className="swiper-button-next swiper-button-white"></div>
<div className="swiper-button-prev swiper-button-white"></div>
</div>
</div>
)
}
}
export default App
Vue: App.vue文件
<template>
<div id="app">
<div class="switch-data">
<span @click="switchData(10000)">10000</span>
<span @click="switchData(5000)">5000</span>
<span @click="switchData(2000)">2000</span>
<span @click="switchData(1000)">1000</span>
<span @click="switchData(100)">100</span>
</div>
<div class="swiper-container gallery-top">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="(item, key) in arrList" :key="key">
<p>key: {{key}}</p>
<p>value: {{item.a}}</p>
<img src="./assets/logo.png" alt="">
</div>
</div>
<!-- Add Arrows -->
<div class="swiper-button-next swiper-button-white"></div>
<div class="swiper-button-prev swiper-button-white"></div>
</div>
</div>
</template>
<script>
// import HelloWorld from './components/HelloWorld.vue'
import Swiper from 'swiper'
export default {
name: 'app',
data () {
return {
arrList: new Array(1000).fill({a: 1000}),
options: {
spaceBetween: 10,
loop:true,
freeMode: true,
loopedSlides: 5, //looped slides should be the same
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
}
}
},
mounted() {
this.initSwiper()
},
beforeUpdate () {
this.start = new Date().getTime()
if (this.swiper) {
this.swiper.destroy()
}
},
updated() {
this.initSwiper()
this.end = new Date().getTime()
console.log('end: ', this.end - this.start)
},
methods: {
initSwiper () {
this.swiper = new Swiper('.gallery-top', this.options)
},
switchData (val) {
this.arrList = new Array(val).fill({a: val})
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
html, body {
position: relative;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 48px;
color:#000;
margin: 0;
padding: 0;
}
.swiper-container {
width: 100%;
margin-left: auto;
margin-right: auto;
height: 100vh;
}
.swiper-slide {
background-size: cover;
background-position: center;
border: 1px solid #000;
height: 300px;
text-align: center;
}
.gallery-top {
width: 100%;
}
.gallery-thumbs {
box-sizing: border-box;
padding: 10px 0;
}
.gallery-thumbs .swiper-slide {
opacity: 0.4;
}
.gallery-thumbs .swiper-slide-thumb-active {
opacity: 1;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.switch-data {
position: fixed;
left: 100px;
top: 100px;
z-index: 10;
}
.switch-data span {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #000000;
background: #000000;
color: #fff;
padding: 10px 30px;
margin-bottom: 20px;
}
</style>