Skip to content

refactor(reactivity): encapsulate reactive handlers in class #8586

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 22, 2023

Conversation

Waleed-KH
Copy link
Contributor

encapsulate reactive handlers in class:

  • BaseReactiveHandler: has the get function that all reactive objects use
  • MutableReactiveHandler: extend BaseReactiveHandler with set, deleteProperty, has, ownKeys
  • ReadonlyReactiveHandler: extend BaseReactiveHandler with set & deleteProperty warners

@sxzz sxzz added ready to merge The PR is ready to be merged. ready for review This PR requires more reviews labels Aug 13, 2023
@sxzz
Copy link
Member

sxzz commented Aug 14, 2023

/ecosystem-ci run

@sxzz sxzz force-pushed the reactive-handler branch from 763609b to 99e3705 Compare August 14, 2023 08:48
@vue-bot
Copy link
Contributor

vue-bot commented Aug 14, 2023

📝 Ran ecosystem CI: Open

suite result
nuxt ✅ success
pinia ✅ success
quasar ❌ failure
router ✅ success
test-utils ✅ success
vant ✅ success
vite-plugin-vue ✅ success
vitepress ✅ success
vue-i18n ✅ success
vue-macros ❌ failure
vuetify ✅ success
vueuse ✅ success
vue-simple-compiler ✅ success

@sxzz
Copy link
Member

sxzz commented Aug 14, 2023

Thank you for your excellent work! I would like to know what motivated your PR. Does it have any impact on performance? If so, could you please provide benchmark results to validate this point? I am also concerned about whether it will increase the size of the bundle. It seems that classes are more difficult to tree-shake compared to functions.

If you can provide some evidence that your PR will make Vue better, we would be more than happy to merge it! ❤️

@Waleed-KH
Copy link
Contributor Author

Thanx for the replay, this my first contribute to vue and I was trying to understand how the core works. My main motivation here is to organize the code to make it easier to understand and work with.

Before this PR createGetter & createSetter returned functions with different parameters, so they kinda worked like a new class initializer, I thought it would be better to encapsulate them in a class that also implements the ProxyHandler needed.

As for impact on performance, I'm not a JS expert, but I believe this code should allocate and use less memory, if you can guide me how, I would like to test it and get some benchmark results to validate this point.

As for bundle size concern, this was the main reason I separated them to 3 classes, if only readonlyHandlers was used, MutableReactiveHandler would be tree-shaked, (sample with rollup).

@edison1105
Copy link
Member

edison1105 commented Aug 15, 2023

I did a simple benchmark test via https://github.com/basvanmeurs/vue-next-benchmarks:

Based on the latest version

Use Vue3: https://cdnjs.cloudflare.com/ajax/libs/vue/3.3.4/vue.global.js
Benchmarks: reactiveObject
Available: ref,computed,watch,watchEffect,mix,reactiveObject,reactiveMap,reactiveArray
vue.global.js:10552 You are running a development build of Vue.
Make sure to use the production build (*.prod.js) when deploying for production.
Benchmark: reactiveObject
-create reactive obj x 183,736 ops/sec ±164.46% (5 runs sampled)
write reactive obj property x 4,866,484 ops/sec ±0.42% (68 runs sampled)
write reactive obj, don't read computed (never invoked) x 4,872,878 ops/sec ±0.26% (69 runs sampled)
write reactive obj, don't read computed (invoked) x 3,021,506 ops/sec ±0.25% (66 runs sampled)
write reactive obj, read computed x 2,183,469 ops/sec ±0.18% (67 runs sampled)
write reactive obj, don't read 1000 computeds (never invoked) x 4,812,272 ops/sec ±0.20% (67 runs sampled)
write reactive obj, don't read 1000 computeds (invoked) x 42,809 ops/sec ±0.24% (69 runs sampled)
write reactive obj, read 1000 computeds x 6,859 ops/sec ±0.34% (66 runs sampled)
1000 reactive objs, 1 computed x 11,690 ops/sec ±0.58% (63 runs sampled)

Based on this PR

Use Vue3: ./vue.global.js
Benchmarks: reactiveObject
Available: ref,computed,watch,watchEffect,mix,reactiveObject,reactiveMap,reactiveArray
vue.global.js:10552 You are running a development build of Vue.
Make sure to use the production build (*.prod.js) when deploying for production.
Benchmark: reactiveObject
+create reactive obj x 574,722 ops/sec ±51.86% (13 runs sampled)
write reactive obj property x 4,636,881 ops/sec ±0.25% (69 runs sampled)
write reactive obj, don't read computed (never invoked) x 4,620,616 ops/sec ±0.24% (69 runs sampled)
write reactive obj, don't read computed (invoked) x 2,687,439 ops/sec ±3.70% (61 runs sampled)
write reactive obj, read computed x 2,051,574 ops/sec ±1.19% (68 runs sampled)
write reactive obj, don't read 1000 computeds (never invoked) x 4,495,859 ops/sec ±0.34% (67 runs sampled)
write reactive obj, don't read 1000 computeds (invoked) x 41,264 ops/sec ±2.62% (66 runs sampled)
write reactive obj, read 1000 computeds x 6,645 ops/sec ±2.37% (38 runs sampled)
1000 reactive objs, 1 computed x 11,758 ops/sec ±0.32% (68 runs sampled)

@Justineo
Copy link
Member

We need some more objective reasons to refactor existing code, such as improvements on performance or maintainability and need to ensure the refactoring doesn't introduce negative impacts on performance.

@sxzz sxzz requested a review from yyx990803 August 15, 2023 04:14
@Waleed-KH
Copy link
Contributor Author

Waleed-KH commented Aug 15, 2023

I've done some benchmark tests (same by @edison1105) and got similar results:

  • reactive obj create has a huge positive impact (200% - 700%)
  • get/set reactive obj props has small negative impact (1% - 5%)
Benchmark Result (this PR) Result (commit 623ba51) Ratio
create reactive obj 1,152,329 ops/sec ±57.45% (15 runs sampled) 156,139 ops/sec ±126.78% (7 runs sampled) +638.01%
write reactive obj property 4,224,204 ops/sec ±0.29% (68 runs sampled) 4,398,603 ops/sec ±0.20% (68 runs sampled) -3.96%
write reactive obj, don't read computed (never invoked) 4,240,543 ops/sec ±0.31% (68 runs sampled) 4,391,671 ops/sec ±0.25% (67 runs sampled) -3.44%
write reactive obj, don't read computed (invoked) 2,879,259 ops/sec ±0.77% (67 runs sampled) 2,968,753 ops/sec ±0.23% (68 runs sampled) -3.01%
write reactive obj, read computed 2,034,185 ops/sec ±0.23% (67 runs sampled) 2,103,718 ops/sec ±0.21% (68 runs sampled) -3.31%
write reactive obj, don't read 1000 computeds (never invoked) 4,195,139 ops/sec ±0.42% (67 runs sampled) 4,306,288 ops/sec ±0.24% (69 runs sampled) -2.58%
write reactive obj, don't read 1000 computeds (invoked) 57,195 ops/sec ±0.58% (66 runs sampled) 57,932 ops/sec ±0.63% (66 runs sampled) -1.27%
write reactive obj, read 1000 computeds 8,353 ops/sec ±0.30% (68 runs sampled) 8,378 ops/sec ±0.27% (67 runs sampled) -0.3%
1000 reactive objs, 1 computed 16,125 ops/sec ±0.56% (67 runs sampled) 16,963 ops/sec ±0.47% (64 runs sampled) -4.94%

Note

I did multiple runs for the benchmark, the table above is the average results I got that was also close to @edison1105 test, following is the best result I got for create reactive obj:

  • this PR: 5,439,439 ops/sec ±8.71% (48 runs sampled)
  • commit 623ba51: 638,106 ops/sec ±89.24% (10 runs sampled)

As for the size impact, the build min files has increased a little (~0.05kb), but gzip & brotli are smaller

  • this PR:

    reactivity.global.prod.js min:12.01kb / gzip:4.42kb / brotli:4.08kb
    runtime-dom.global.prod.js min:83.82kb / gzip:31.80kb / brotli:28.71kb
    vue.global.prod.js min:128.50kb / gzip:48.14kb / brotli:43.18kb
    vue.runtime.global.prod.js min:83.82kb / gzip:31.80kb / brotli:28.73kb
    
  • commit 623ba51:

    reactivity.global.prod.js min:11.96kb / gzip:4.44kb / brotli:4.09kb
    runtime-dom.global.prod.js min:83.76kb / gzip:31.84kb / brotli:28.72kb
    vue.global.prod.js min:128.45kb / gzip:48.17kb / brotli:43.27kb
    vue.runtime.global.prod.js min:83.77kb / gzip:31.83kb / brotli:28.77kb
    

@sxzz sxzz removed the ready to merge The PR is ready to be merged. label Aug 21, 2023
@sxzz sxzz force-pushed the reactive-handler branch from d8c6132 to 127e078 Compare August 21, 2023 08:07
@github-actions
Copy link

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 85.8 kB (+52 B) 32.6 kB (-33 B) 29.4 kB (-18 B)
vue.global.prod.js 132 kB (+52 B) 49.3 kB (-23 B) 44.2 kB (-92 B)

Usages

Name Size Gzip Brotli
createApp 47.8 kB (+72 B) 18.8 kB (-19 B) 17.2 kB (-24 B)
createSSRApp 50.6 kB (+72 B) 19.9 kB (-15 B) 18.2 kB (-24 B)
defineCustomElement 50.2 kB (+72 B) 19.6 kB (-6 B) 18 kB (+42 B)
overall 61.2 kB (+72 B) 23.7 kB (-3 B) 21.5 kB (-24 B)

@sxzz sxzz added ready to merge The PR is ready to be merged. and removed ready for review This PR requires more reviews labels Aug 22, 2023
@sxzz sxzz merged commit 02c6924 into vuejs:main Aug 22, 2023
@Waleed-KH Waleed-KH deleted the reactive-handler branch August 22, 2023 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready to merge The PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants