So sánh tốc độ giữa VueJS vs ReactJS
May 2, 2018
Môi trường
Macbook Pro 2017 15′ – 2.8 GHz Intel Core i7 – 16 GB
macOS Sierra 10.16.6
Google Chrome 66
React v16.3.2
VueJS v2.5.3
Thiết lập
Tạo project mới hoàn toàn từ create-react-app và vue-cli
npx create-react-app benchmark vue init webpack benchmark
Tất cả benchmark logic viết trong 1 file component duy nhất
Library hỗ trợ
- Bootstrap 4.1.0
- Lodash 4.17.5
- Faker 4.1.0
Các mục so sánh
- Create n row sau khi load page
- Replace toàn bộ row sau khi tạo
- Select 1 row để highlight (5 lần chuẩn bị)
- Update nội dung tất cả các row thứ 10, 20, 30 … (5 lần chuẩn bị)
- Delete 1 row (5 lần chuẩn bị)
- Swap 2 row (5 lần chuẩn bị)
- Append 1000 row vào cuối bảng
- Clear all
Thực hiện benchmark với cài đặt có sử dụng key và không sử dụng key khi render bảng
Kết quả
Nhận xét
==> React nhìn chung chậm hơn VueJS
- Hầu hết các hạng mục VueJS đều nhanh hơn React chỉ trừ clear all
- VueJS đặc biệt nhanh hơn ở 2 mục select và swap, có thể lên đến
400% - Tại mốc 100,000 row, React mất hơn
90sđể Create còn VueJS chỉ mất dưới20s - Giữa
keyedvànon-keyedcủa React nhìn chung có sự khác biệtkeyednhanh hơn ở các tác vụappendvàdeletenon-keyednhanh hơn ở các tác vụ khác- tuy nhiên khi số lượng row nhiều lên thì
keyedchậm đi nhiều hơn
keyedvànon-keyedcủa VueJS nhìn chung là tương đương
Source code
React
import React, { Component } from 'react';
import _ from 'lodash'
import faker from 'faker'
import './App.css';
let startTime
let lastMeasure
const startMeasure = (name) => {
startTime = performance.now()
lastMeasure = name
}
const stopMeasure = () => {
const last = lastMeasure
if (lastMeasure) {
setTimeout(() => {
lastMeasure = null
const stop = performance.now()
console.log(last + " took " + (stop - startTime))
}, 0);
}
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
items: [],
amount: 100000,
selectedId: '',
}
this.create = this.create.bind(this)
this.removeItem = this.removeItem.bind(this)
this.clear = this.clear.bind(this)
this.append = this.append.bind(this)
this.update = this.update.bind(this)
this.swap = this.swap.bind(this)
this.createCustom = this.createCustom.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
}
printDuration() {
stopMeasure();
}
componentDidUpdate() {
this.printDuration();
}
componentDidMount() {
this.printDuration();
}
generateData(amount = 1000, lastIndex = 0) {
return _.times(amount, (index) => (
{
id: (lastIndex + index + 1),
name: faker.commerce.productName(),
}
))
}
create(amount = 1000) {
startMeasure(`create ${amount}`)
this.setState({ selectedId: '', items: this.generateData(amount) })
}
createCustom() {
const amount = this.state.amount
if (!isNaN(amount) && amount > 0) {
this.create(amount)
}
}
removeItem(index) {
startMeasure('remove one')
const items = _.concat(this.state.items.slice(0, index), this.state.items.slice(index+1, this.state.items.length))
this.setState({ items })
}
clear() {
startMeasure('clear all')
this.setState({ items: [] })
}
append(amount = 1000) {
startMeasure(`append ${amount}`)
const items = _.concat(this.state.items, this.generateData(amount, _.isEmpty(this.state.items) ? 0 : _.last(this.state.items).id))
this.setState({ items })
}
update(pos) {
startMeasure(`update`)
const items = _.map(this.state.items, (item) => {
if (item.id % pos === 0) {
return {id: item.id, name: item.name + ' !!!'}
}
return item
})
this.setState({ items })
}
swap() {
startMeasure('swap')
const items = _.map(this.state.items, (item, index) => {
if (index === 4) {
return this.state.items[9]
} else if (index === 9) {
return this.state.items[4]
}
return item
})
this.setState({ items })
}
select(id) {
startMeasure('select')
this.setState({ selectedId: id })
}
listItems() {
const { items, selectedId } = this.state
return items.map((item, index) => (
<tr className={selectedId === item.id ? 'selected' : ''}>
<td>{ item.id }</td>
<td onClick={() => this.select(item.id)}>{ item.name }</td>
<td><button type="button" className="btn btn-outline-danger btn-small" onClick={() => this.removeItem(index)}>x</button></td>
</tr>
))
}
handleInputChange(event) {
event.preventDefault()
const value = event.target.value
this.setState({ amount: value })
}
render() {
return (
<div className="container">
<div className="jumbotron">
<div className="row">
<div className="col-md-6">
<h1>React 16.3.2</h1>
</div>
<div className="col-md-6">
<div className="row">
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={() => this.create(1000)}>Create 1000 rows</button>
</div>
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={() => this.create(10000)}>Create 10000 rows</button>
</div>
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={() => this.append(1000)}>Append 1000 rows</button>
</div>
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={() => this.update(10)}>Update every 10th rows</button>
</div>
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={this.clear}>Clear</button>
</div>
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={this.swap}>Swap rows</button>
</div>
<div className="col-md-6 smallpad">
<input type="number" className="form-control" value={this.state.amount} onChange={this.handleInputChange} onBlur={this.handleInputChange} />
</div>
<div className="col-md-6 smallpad">
<button type="button" className="btn btn-info btn-block" onClick={this.createCustom}>{`Create { ${this.state.amount} } rows`}</button>
</div>
</div>
</div>
</div>
</div>
<br />
<table className="table">
<tbody>
{this.listItems()}
</tbody>
</table>
</div>
);
}
}
export default App;
Vue
<template>
<div class="container">
<div class="jumbotron">
<div class="row">
<div class="col-md-6">
<h1>VueJS 2.5.2</h1>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="create(1000)">Create 1000 rows</button>
</div>
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="create(10000)">Create 10000 rows</button>
</div>
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="append(1000)">Append 1000 rows</button>
</div>
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="update(10)">Update every 10th rows</button>
</div>
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="clear()">Clear</button>
</div>
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="swap()">Swap rows</button>
</div>
<div class="col-md-6 smallpad">
<input type="number" class="form-control" v-model="amount">
</div>
<div class="col-md-6 smallpad">
<button type="button" class="btn btn-info btn-block" @click="createCustom()">Create { {{amount}} } rows</button>
</div>
</div>
</div>
</div>
</div>
<br>
<table class="table">
<tbody>
<tr v-for="(item, index) in items" :key="item.id" :class="item.id === selectedId ? 'selected' : ''">
<td>{{ item.id }}</td>
<td @click="select(item.id)">{{ item.name }}</td>
<td><button type="button" class="btn btn-outline-danger btn-small" @click="removeItem(index)">x</button></td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import faker from 'faker'
import _ from 'lodash'
let startTime
let lastMeasure
const startMeasure = (name) => {
startTime = performance.now()
lastMeasure = name
}
const stopMeasure = () => {
const last = lastMeasure
if (lastMeasure) {
setTimeout(() => {
lastMeasure = null
const stop = performance.now()
console.log(last + " took " + (stop - startTime))
}, 0);
}
}
export default {
name: 'HelloWorld',
data () {
return {
items: [],
selectedId: '',
amount: 100000,
}
},
methods: {
generateData(amount = 1000, lastIndex = 0) {
return _.times(amount, (index) => (
{
id: (lastIndex + index + 1),
name: faker.commerce.productName(),
}
))
},
select(id) {
startMeasure('select')
this.selectedId = id
stopMeasure()
},
create(amount = 1000) {
startMeasure(`creat ${amount}`)
this.selectedId = ''
this.items = this.generateData(amount)
stopMeasure()
},
createCustom() {
this.create(this.amount)
},
removeItem(index) {
startMeasure('remove row')
this.items = _.concat(this.items.slice(0, index), this.items.slice(index+1, this.items.length))
stopMeasure()
},
clear() {
startMeasure('clear all')
this.items = []
stopMeasure()
},
append(amount = 1000) {
startMeasure(`append ${amount}`)
this.items = _.concat(this.items, this.generateData(amount, _.isEmpty(this.items) ? 0 : _.last(this.items).id))
stopMeasure()
},
update(pos) {
startMeasure('update')
this.items = _.map(this.items, (item) => {
if (item.id % pos === 0) {
return {id: item.id, name: item.name + ' !!!'}
}
return item
})
stopMeasure()
},
swap() {
startMeasure('swap')
this.items = _.map(this.items, (item, index) => {
if (index === 4) {
return this.items[9]
} else if (index === 9) {
return this.items[4]
}
return item
})
stopMeasure()
}
}
}
</script>
<style scoped>
.smallpad {
margin: 5px 0;
}
.jumbotron {
padding: 2rem !important;
}
tr.selected {
background-color: lightpink;
}
</style>
Tham khảo
http://www.stefankrause.net/js-frameworks-benchmark6/webdriver-ts-results/table.html



