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
keyed
vànon-keyed
của React nhìn chung có sự khác biệtkeyed
nhanh hơn ở các tác vụappend
vàdelete
non-keyed
nhanh hơn ở các tác vụ khác- tuy nhiên khi số lượng row nhiều lên thì
keyed
chậm đi nhiều hơn
keyed
vànon-keyed
củ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