So sánh tốc độ giữa VueJS vs ReactJS
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
1 2 3 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
<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