@@ -29,6 +29,10 @@ const buildStateHook = template(`
29
29
const [STATE_PROP, STATE_SETTER] = useState(STATE_VALUE);
30
30
` ) ;
31
31
32
+ const buildRefHook = template ( `
33
+ const VAR_NAME = useRef(INITIAL_VALUE);
34
+ ` ) ;
35
+
32
36
const buildEffectHook = template ( `
33
37
useEffect(() => { EFFECT });
34
38
` ) ;
@@ -40,11 +44,37 @@ useCallback(CALLBACK);
40
44
export function statefulToStateless ( component ) {
41
45
const functionBody = [ ] ;
42
46
const stateProperties = new Map ( ) ;
43
-
47
+ const refProperties = new Map ( ) ;
44
48
const RemoveThisVisitor = {
45
49
MemberExpression ( path ) {
46
- if ( t . isThisExpression ( path . node . object ) ) {
47
- path . replaceWith ( path . node . property ) ;
50
+ if ( path . node . wasVisited || path . shouldSkip ) return ;
51
+ if (
52
+ isHooksForFunctionalComponentsExperimentOn ( ) &&
53
+ path . key !== "callee"
54
+ ) {
55
+ if (
56
+ t . isIdentifier ( path . node . property ) &&
57
+ ! [ "state" , "props" ] . includes ( path . node . property . name )
58
+ ) {
59
+ if ( ! refProperties . has ( path . node . property . name ) ) {
60
+ refProperties . set ( path . node . property . name , undefined ) ;
61
+ }
62
+ }
63
+
64
+ const replacement = t . memberExpression (
65
+ t . identifier ( path . node . property . name ) ,
66
+ t . identifier ( "current" )
67
+ ) ;
68
+
69
+ ( replacement as any ) . wasVisited = true ;
70
+
71
+ path . replaceWith ( replacement ) ;
72
+
73
+ path . skip ( ) ;
74
+ } else {
75
+ if ( t . isThisExpression ( path . node . object ) ) {
76
+ path . replaceWith ( path . node . property ) ;
77
+ }
48
78
}
49
79
}
50
80
} ;
@@ -75,48 +105,41 @@ export function statefulToStateless(component) {
75
105
76
106
const RemoveSetStateAndForceUpdateVisitor = {
77
107
CallExpression ( path ) {
78
- if ( t . isMemberExpression ( path . node . callee ) ) {
79
- if ( t . isThisExpression ( path . node . callee . object ) ) {
80
- if ( isHooksForFunctionalComponentsExperimentOn ( ) ) {
81
- if ( path . node . callee . property . name === "forceUpdate" ) {
82
- path . remove ( ) ;
83
- } else if ( path . node . callee . property . name === "setState" ) {
84
- const buildRequire = template ( `
108
+ if (
109
+ t . isMemberExpression ( path . node . callee ) &&
110
+ t . isThisExpression ( path . node . callee . object )
111
+ ) {
112
+ if ( isHooksForFunctionalComponentsExperimentOn ( ) ) {
113
+ if ( path . node . callee . property . name === "forceUpdate" ) {
114
+ path . remove ( ) ;
115
+ } else if ( path . node . callee . property . name === "setState" ) {
116
+ const buildRequire = template ( `
85
117
STATE_SETTER(STATE_VALUE);
86
118
` ) ;
87
119
88
- if (
89
- t . isFunctionExpression ( path . node . arguments [ 0 ] ) ||
90
- t . isArrowFunctionExpression ( path . node . arguments [ 0 ] )
91
- ) {
92
- handleFunctionalStateUpdate ( path , buildRequire , stateProperties ) ;
93
- } else {
94
- path . node . arguments [ 0 ] . properties . forEach ( ( { key, value } ) => {
95
- path . insertBefore (
96
- buildRequire ( {
97
- STATE_SETTER : t . identifier (
98
- `set${ capitalizeFirstLetter ( key . name ) } `
99
- ) ,
100
- STATE_VALUE : value
101
- } )
102
- ) ;
103
-
104
- if ( ! stateProperties . has ( key . name ) ) {
105
- stateProperties . set ( key . name , void 0 ) ;
106
- }
107
- } ) ;
108
- }
109
-
110
- path . remove ( ) ;
111
- }
112
- } else {
113
- if (
114
- [ "setState" , "forceUpdate" ] . indexOf (
115
- path . node . callee . property . name
116
- ) !== - 1
117
- ) {
118
- path . remove ( ) ;
120
+ if ( isStateChangedThroughFunction ( path . node . arguments [ 0 ] ) ) {
121
+ covertStateChangeThroughFunction (
122
+ path ,
123
+ buildRequire ,
124
+ stateProperties
125
+ ) ;
126
+ } else {
127
+ convertStateChangeThroughObject (
128
+ path ,
129
+ buildRequire ,
130
+ stateProperties
131
+ ) ;
119
132
}
133
+
134
+ path . remove ( ) ;
135
+ }
136
+ } else {
137
+ if (
138
+ [ "setState" , "forceUpdate" ] . indexOf (
139
+ path . node . callee . property . name
140
+ ) !== - 1
141
+ ) {
142
+ path . remove ( ) ;
120
143
}
121
144
}
122
145
}
@@ -288,6 +311,8 @@ export function statefulToStateless(component) {
288
311
t . isArrowFunctionExpression ( propValue )
289
312
) {
290
313
copyNonLifeCycleMethods ( path ) ;
314
+ } else {
315
+ refProperties . set ( path . node . key . name , path . node . value ) ;
291
316
}
292
317
if ( t . isObjectExpression ( propValue ) && path . node . key . name === "state" ) {
293
318
( propValue . properties as t . ObjectProperty [ ] ) . map ( ( { key, value } ) => {
@@ -319,6 +344,17 @@ export function statefulToStateless(component) {
319
344
traverse ( ast , visitor ) ;
320
345
321
346
if ( isHooksForFunctionalComponentsExperimentOn ( ) ) {
347
+ const refHookExpression = Array . from ( refProperties ) . map (
348
+ ( [ key , defaultValue ] ) => {
349
+ return buildRefHook ( {
350
+ VAR_NAME : t . identifier ( key ) ,
351
+ INITIAL_VALUE : defaultValue
352
+ } ) ;
353
+ }
354
+ ) ;
355
+
356
+ functionBody . unshift ( ...refHookExpression ) ;
357
+
322
358
if ( effectBody || effectTeardown ) {
323
359
const expressions = [ ] ;
324
360
if ( effectBody ) {
@@ -358,11 +394,41 @@ export function statefulToStateless(component) {
358
394
text : processedJSX ,
359
395
metadata : {
360
396
stateHooksPresent : stateProperties . size > 0 ,
397
+ refHooksPresent : refProperties . size > 0 ,
361
398
nonLifeycleMethodsPresent
362
399
}
363
400
} ;
364
401
}
365
- function handleFunctionalStateUpdate ( path : any , buildRequire : any , stateProperties ) {
402
+ function isStateChangedThroughFunction ( setStateArg : any ) {
403
+ return (
404
+ t . isFunctionExpression ( setStateArg ) ||
405
+ t . isArrowFunctionExpression ( setStateArg )
406
+ ) ;
407
+ }
408
+
409
+ function convertStateChangeThroughObject (
410
+ path : any ,
411
+ buildRequire : any ,
412
+ stateProperties : Map < any , any >
413
+ ) {
414
+ path . node . arguments [ 0 ] . properties . forEach ( ( { key, value } ) => {
415
+ path . insertBefore (
416
+ buildRequire ( {
417
+ STATE_SETTER : t . identifier ( `set${ capitalizeFirstLetter ( key . name ) } ` ) ,
418
+ STATE_VALUE : value
419
+ } )
420
+ ) ;
421
+ if ( ! stateProperties . has ( key . name ) ) {
422
+ stateProperties . set ( key . name , void 0 ) ;
423
+ }
424
+ } ) ;
425
+ }
426
+
427
+ function covertStateChangeThroughFunction (
428
+ path : any ,
429
+ buildRequire : any ,
430
+ stateProperties
431
+ ) {
366
432
const stateProducer = path . node . arguments [ 0 ] ;
367
433
const stateProducerArg = stateProducer . params [ 0 ] ;
368
434
const isPrevStateDestructured = t . isObjectPattern ( stateProducerArg ) ;
@@ -494,10 +560,12 @@ export async function statefulToStatelessComponent() {
494
560
495
561
const {
496
562
stateHooksPresent,
563
+ refHooksPresent,
497
564
nonLifeycleMethodsPresent
498
565
} = selectionProccessingResult . metadata ;
499
566
const usedHooks = [
500
567
...( stateHooksPresent ? [ "useState" ] : [ ] ) ,
568
+ ...( refHooksPresent ? [ "useRef" ] : [ ] ) ,
501
569
...( nonLifeycleMethodsPresent ? [ "useCallback" ] : [ ] )
502
570
] ;
503
571
0 commit comments