A plugin for optimizing stateless component of React (tsx)
After react 16.8, react team announced new feature [Hooks], this plugin cannot work with it as its special runtime mechanism.
React functional component(SFC) is easy to use and help to reduce code size significantly, but sometimes people might have been some misunderstanding about its perfomance. Usually, we think functional components would avoid some overheads like mounting / unmounting / lifecycle checking and memory allocations, but in fact, there're no special optimizations currently (but after react 16 was released, sfc is indeed faster than before).
Fortunately SFC just function in react world, if we do care about performance in production there're still a way to improve and this plugin here come to simplify these situation.
const code1 = (
<div>
<Avatar />
</div>
)
const code2 = (
<div>
{ Avatar() }
</div>
)
As we cannot recognize if the component is functional, we have to use an anotation to tag the expression:
<Avatar sfc />
// Plugin use `sfc` as identifier by default, but you can pass an option to override it.
module: {
rules: [
{
test: /\.(jsx|tsx|js|ts)$/,
loader: 'ts-loader',
options: {
transpileOnly: true,
getCustomTransformers: () => ({
before: [sfcPlugin()],
}),
compilerOptions: {
module: 'esnext',
},
},
exclude: /node_modules/,
}
],
}
import React from 'react'
export const Avatar = ({ name }) => {
return (
<div>
<img src=... />
<span>{ name }</span>
</div>
)
}
import React from 'react'
import { Avatar } from './avatar.component'
export class App extends React.PureComponent {
render() {
return (
<div>
<Avatar name={ 'hello world' } sfc />
</div>
)
}
}
sfcPlugin(option?: Option)
interface Option {
pragma?: string
mode?: 1 | 2
}
Reason | Deopt (mode 1) | Deopt (mode 2) |
---|---|---|
spread operator | true | false |
prop: key | true | false |
Considering we transform the code from tsx to native function-call which means the additional vd-layer will be eliminatd, and the effects of key
will be removed as well.
// before
const Message = () => <div>bravo</div>
export class App extends React.PureComponent {
render() {
return <Message key={ 1 } />
}
}
// after
const Message = () => <div>bravo</div>
export class App extends React.PureComponent {
render() {
// won't get benefit from prop: `key`
return Message()
}
}
Unlike @babel/plugin-transform-react-inline-elements, we won't take ref
into account because of this plugin will be applied to typescript only.
const Message = () => <div>bravo</div>
export class App extends React.PureComponent {
render() {
// ERROR: this is not type-safe
// { ref: any } is not assignable to IntrinsicAttributes
return <Message ref={ ... } />
}
}
The following code is recommanded:
<Avatar sfc>
// enable rule: `jsx-boolean-value` in tslint.json
using declaration merging in global .d.ts
import React from 'react'
declare module 'react' {
namespace JSX {
interface IntrinsicAttributes extends React.Attributes {
sfc?: boolean
}
}
}
code like the usage will not work, because the plugin does not include any runtime type checking.
const component = <Avatar sfc={ this.props.flag } />
React 16.4, <Dot />
, 50 times, MacBook Pro (Retina, 13-inch, Early 2013)
Classical | Functional | Direct-call | Auto-transform |
---|---|---|---|
660ms | 408ms | 226ms | 229ms |