) => {
69 | setPoolSize(Number(event.target.value))
70 | }}
71 | />
72 | )}
73 | rowRendererProvider={(base, pow) => ({ key, index, style }) => (
74 |
82 | )}
83 | />
84 | >
85 | )
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/molecules/SomeInput.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import TextField from '@material-ui/core/TextField'
4 | import { AppCtx, AppMode } from '../../contexts/AppCtx'
5 | import { FormControl } from '@material-ui/core'
6 | import InputLabel from '@material-ui/core/InputLabel'
7 | import Select from '@material-ui/core/Select'
8 |
9 | const useStyles = makeStyles((theme) => ({
10 | root: {
11 | '& > *': {
12 | margin: theme.spacing(1),
13 | width: '25ch',
14 | },
15 | },
16 | }))
17 |
18 | export default function SomeInput() {
19 | const classes = useStyles()
20 | const appCtx = useContext(AppCtx)
21 | if (appCtx === null) {
22 | return null
23 | }
24 | const { input, setInput, mode, setMode } = appCtx
25 |
26 | return (
27 |
97 | )
98 | }
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Offload UI Thread Research
2 |
3 | When we develop a frontend application, we undoubtedly offload all kinds of I/O,
4 | computational tasks from UI thread to prevent UI thread is too busy and become
5 | unresponsive. **However, this rule doesn't apply to current web development**. The
6 | current web developmennt **ONLY** offload the tasks to web worker when the application
7 | encounter performance issues but **NOT** by the tasks' nature.
8 |
9 | [Live Demo](https://gaplo917.github.io/react-offload-ui-thread-research/?v=2)
10 |
11 | ### 120Hz is coming
12 |
13 | 120Hz Web browsing is coming, the higher fps the shorter time for UI thread to process.
14 | Consider the 60Hz to 120Hz change, the "smooth UI" process cycle time changed from 16.67ms
15 | to 8.33ms, that halve the time we got from previous decade!
16 |
17 | > An I/O call is non-blocking on UI thread doesn't mean that it doesn't use the UI thread
18 | > CPU time.
19 |
20 | In addition, the business requirements, validations, UI events also become more and more
21 | complex. If you need to build a hassle-free smooth 120Hz Web application, using web workers
22 | are unavoidable.
23 |
24 | ### Painful and Time-consuming web worker development
25 |
26 | Because of the learning curve of web workers, we are tempted to do everything
27 | on UI thread like:
28 |
29 | - calling fragmented REST call and then **aggregate together**
30 | - calling a large GraphQL query and then apply **data transformation**
31 | - sorting, filtering and reduce differents kinds of data triggered by UI actions
32 |
33 | It is because:
34 |
35 | - creating a web-worker and import existing npm modules into web worker is **painful**.
36 | - coding in a complete message driven style is **not intuitive, time-consuming, and repetitive**.
37 | - working in an **async UI pattern** requires more state to handle it.
38 |
39 | ### Upcoming Possibility
40 |
41 | As the [ComLink abstraction](https://github.com/GoogleChromeLabs/comlink)(turn a web
42 | worker to RPC-style function call) and
43 | [React Concurrent mode](https://reactjs.org/docs/concurrent-mode-intro.html) arise. I
44 | think it is time to start thinking to adopt web worker **_in all cases_** - **completely
45 | decouple a data accessing layer**, use browser's background thread for
46 | **ALL** I/O and data processing bu nature and then return to the UI thread for rendering.
47 |
48 | Nothing is new, this was how we wrote a standard frontend application in other
49 | platforms(iOS, Android, Windows, macOS, JVM) since multi-threaded CPU appeared.
50 |
51 | This project is a
52 | [Comlink loader (Webpack)](https://github.com/GoogleChromeLabs/comlink-loader) decision
53 | research.
54 |
55 | To access the complete research findings(WIP), you could access in
56 | [patreon](https://www.patreon.com/gaplotech).
57 |
58 | ## Getting Started
59 |
60 | [](https://codesandbox.io/s/github/gaplo917/react-offload-ui-thread-research/tree/master/?fontsize=14&hidenavigation=1)
61 |
62 | or try locally
63 |
64 | ```
65 | git clone https://github.com/gaplo917/react-offload-ui-thread-research.git
66 |
67 | cd react-offload-ui-thread-research
68 | yarn intall
69 |
70 | # start the demo
71 | yarn start
72 | ```
73 |
--------------------------------------------------------------------------------
/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { hot } from 'react-hot-loader'
3 | import SomeListSingleton from './organisms/SomeListSingleton'
4 | import SomeListBlocking from './organisms/SomeListBlocking'
5 | import SomeInput from './molecules/SomeInput'
6 | import { AppCtx, AppMode, InputModel } from '../contexts/AppCtx'
7 | import CircularProgress from '@material-ui/core/CircularProgress'
8 | import SomeListDedicatedWorker from './organisms/SomeListDedicatedWorker'
9 | import SomeListWorkerPool from './organisms/SomeListWorkerPool'
10 | import { compute } from '../workers/compute'
11 | import { Box } from '@material-ui/core'
12 | import './app.css'
13 |
14 | const ModeSwitcher = ({ mode }: { mode: AppMode }) => (
15 | <>
16 | {mode === AppMode.blocking && }
17 | {mode === AppMode.webWorkerSingleton && }
18 | {mode === AppMode.webWorkerDedicated && }
19 | {mode === AppMode.webWorkerPool && }
20 | >
21 | )
22 |
23 | const Header = () => (
24 | <>
25 | React Offload UI Thread Research
26 |
27 |
28 |
29 | * The progress loading dialog is to track UI blocking occurrence
30 | visually
31 |
32 |
33 |
34 |
35 | Parameters
36 |
37 |
38 | Start tuning the parameters to see the UI blocking!{' '}
39 |
40 |
41 | >
42 | )
43 |
44 | const FunctionPreview = ({ input }: { input: InputModel }) => (
45 |
46 |
47 |
48 | {'//'} each compute run with {input.base}^{input.pow}(
49 | {Math.ceil(Math.pow(input.base, input.pow))}) iterations.
50 |
51 | {String(compute)}
52 |
53 |
54 | )
55 |
56 | const Footer = () => (
57 | <>
58 | More
59 |
60 | GitHub Repository:{' '}
61 |
66 | https://github.com/gaplo917/react-offload-ui-thread-research
67 |
68 |
69 |
70 | For detail explanations and more technical R&D, you can visit{' '}
71 |
76 | https://www.patreon.com/gaplotech
77 |
78 |
79 | >
80 | )
81 | function App() {
82 | const [input, setInput] = useState({
83 | base: 150,
84 | pow: 2.7,
85 | rowCount: 300,
86 | })
87 | const [mode, setMode] = useState(AppMode.blocking)
88 |
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | )
99 | }
100 |
101 | declare let module: Record
102 |
103 | export default hot(module)(App)
104 |
--------------------------------------------------------------------------------