Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

say goodbye to class components

NotificationsYou must be signed in to change notification settings

wantedly/react-declassify

Repository files navigation

This codemod automatically transformsReact class components intoReact functional components using Hooks for you!

BeforeAfter
before example 1after example 1

Features

  • ✅ Supports props, states, methods, and refs.
  • ✅ Comments, spaces, and styles are preserved thanks to therecast library.
  • ✅ Designed to generate as idiomatic code as possible. Not something Babel or Webpack would generate!
  • ✅ Based on classical heuristic automation; no need to be fearful about whimsy LLMs.

Why do we need this?

Class components arestill going to be supported by React for the foreseeable future. However, it is no longer recommended to write new components in class-style.

So what about the existing components? Although React will continue to support these, you may struggle to maintain them because:

  • New libraries and new versions of existing libraries tend to focus on Hooks-style components, and you may find you in a difficulty adopting the components to the libraries.
  • Class components may appear alien to those who are young in React development experience.

Thus it is still a good idea to migrate from class components to Hooks-based components.

However, as this is not a simple syntactic change, migration needs a careful hand work and a careful review. This tool is a classic automation, it reduces a risk of introducing human errors during migration.

Usage

yarn add -D @codemod/cli react-declassify# ORnpm install -D @codemod/cli react-declassify

then

npx codemod --plugin react-declassify 'src/**/*.tsx'

Example

Before:

importReactfrom"react";typeProps={by:number;};typeState={counter:number;};exportclassCextendsReact.Component<Props,State>{staticdefaultProps={by:1};constructor(props){super(props);this.state={counter:0};}render(){return(<><buttononClick={()=>this.onClick()}>{this.state.counter}</button><p>Current step:{this.props.by}</p></>);}onClick(){this.setState({counter:this.state.counter+this.props.by});}}

After:

importReactfrom"react";typeProps={by?:number|undefined};typeState={counter:number;};exportconstC:React.FC<Props>=props=>{const{    by=1}=props;const[counter,setCounter]=React.useState<number>(0);functiononClick(){setCounter(counter+by);}return<><buttononClick={()=>onClick()}>{counter}</button><p>Current step:{by}</p></>;};

Before:

importReactfrom"react";exportclassCextendsReact.Component{render(){const{ text, color}=this.props;return<buttonstyle={{ color}}onClick={()=>this.onClick()}>{text}</button>;}onClick(){const{ text, handleClick}=this.props;alert(`${text} was clicked!`);handleClick();}}

After:

importReactfrom"react";exportconstC=props=>{const{    text,    color,    handleClick}=props;functiononClick(){alert(`${text} was clicked!`);handleClick();}return<buttonstyle={{ color}}onClick={()=>onClick()}>{text}</button>;};

Errors

Hard errors are indicated by/* react-declassify-disable Cannot perform transformation */.

Soft errors are indicated by special variable names including:

  • TODO_this

Hard errors stop transformation of the whole class while stop errors do not. You need to fix the errors to conclude transformation.

Configuration

Disabling transformation

Adding to the class a comment includingreact-declassify-disable will disable transformation of that class.

/* react-declassify-disable */classMyComponentextendsReact.Component{}

Marking the component class asabstract or/** @abstract */ also disables transformation.

Import style

The codemod follows your import style from theextends clause. So

importReactfrom"react";classMyComponentextendsReact.Component{}

is transformed to

importReactfrom"react";constMyComponent:React.FC=()=>{};

whereas

import{Component}from"react";classMyComponentextendsComponent{}

is transformed to

import{Component,FC}from"react";constMyComponent:FC=()=>{};

It cannot be configured to mix these styles. For example it cannot emitReact.FC for typing while emittinguseState (notReact.useState) for hooks.

Receiving refs

Class components may receive refs; this is to be supported in the future. Once it is implemented, you will be able to add special directives in the component to enable the feature.

Syntactic styles

This codemod relies onrecast for pretty-printing and sometimes generates code that does not match your preferred style. This is ineviable. For example it does not currently emit parentheses for the arrow function:

constMyComponent:FC=props=>{//                    ^^^^^ no parentheses// ...};

We have no control over this choice. Even if it were possible, allowing configurations on styles would make the codemod unnecessarily complex.

If you need to enforce specific styles, use Prettier or ESLint or whatever is your favorite to reformat the code after you apply the transformation.

Progress

  • Convert render function (basic feature)
  • Superclass detection
    • SupportReact.Component
    • SupportReact.PureComponent
  • Class node type
    • Support class declarations
    • Supportexport default class declarations
    • Support class expressions
  • TypeScript support
    • AddReact.FC annotation
    • TransformP type argument
    • TransformS type argument
    • Transform ref types
    • Transform generic components
    • Modify Props appropriately if defaultProps is present
    • Modify Props appropriately ifchildren seems to be used
  • Support forthis.props
    • Convertthis.props toprops parameter
    • Renameprops if necessary
    • Hoist expansion ofthis.props
    • Rename prop variables if necessary
    • transformdefaultProps
  • Support for user-defined methods
    • Transform methods tofunctions
    • Transform class fields initialized as functions tofunctions
    • UseuseCallback if deemed necessary
    • Auto-expand direct callback call (likethis.props.onClick()) to indirect call
    • Rename methods if necessary
    • Skip method-binding expressions (e.g.onClick={this.onClick.bind(this)})
    • Skip method-binding statements (e.g.this.onClick = this.onClick.bind(this))
  • Support forthis.state
    • Decomposethis.state intouseState variables
    • Rename states if necessary
    • Support updating multiple states at once
    • Support functional updates
    • Support lazy initialization
  • Support for refs
    • TransformcreateRef touseRef
    • Transform member assignment touseRef
    • Transform legacy string refs as far as possible
  • Support for lifecycles
    • Transform componentDidMount, componentDidUpdate, and componentWillUnmount
      • Support "raw" effects -- simply mapping the three callbacks to guarded effects.
      • Support re-pairing effects
    • Transform shouldComponentUpdate
  • Support for receiving refs
    • UseforwardRef +useImperativeHandle when requested by the user
  • Support for contexts
    • TransformcontextType touseContext
    • Transform the second parameter for the legacycontextTypes
  • Transformstatic propTypes to assignments
  • Rename local variables inrender if necessary

Known limitations

Class refs

Symptom

You get the following type error:

test.tsx:1:1 - error TS2322: Type '{ ... }' is not assignable to type 'IntrinsicAttributes & Props'.  Property 'ref' does not exist on type 'IntrinsicAttributes & Props'.1 ref={ref}  ~~~

or you receive the following warning in the console:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?Check the render method of `C`.    at App

or you receive some sort of null error (e.g.Cannot read properties of undefined (reading 'a')) becauseref.current is always undefined.

Type errors can also occur atuseRef in a component that uses the component under transformation:

test.tsx:1:1 - error TS2749: 'C' refers to a value, but is being used as a type here. Did you mean 'typeof C'?41 const component = React.useRef<C | null>(null);                                  ~

Cause

Class components receives refs, and the ref points to the instance of the class. Functional components do not receive refs by default.

Solution

This is not implemented now. However, once it is implemented you can opt in ref support by certain directives. It will generateforwardRef +useImperativeHandle to expose necessary APIs.

Stricter render types

Symptom

You get the following type error:

test.tsx:1:1 - error TS2322: Type '(props: Props) => ReactNode' is not assignable to type 'FC<Props>'.  Type 'ReactNode' is not assignable to type 'ReactElement<any, any> | null'.1 const C: React.FC<Props> = (props) => {        ~

Cause

In DefinitelyTyped,React.FC is typed slightly stricter than therender method. You are expected a single element ornull.

We leave this untransformed because it is known not to cause problems at runtime.

Solution

An extra layer of a frament<> ... </> suffices to fix the type error.

Assumptions

  • It assumes that the component only needs to reference the latest values ofthis.props orthis.state. This assumption is necessary because there is a difference between class components and funtion components in how the callbacks capture props or states. To transform the code in an idiomatic way, this assumption is necessary.
  • It assumes, by default, the component is always instantiated without refs.
  • It assumes that the methods always receive the samethis value as the one when the method is referenced.
  • It assumes that the component does not update the state conditionally by supplyingundefined tothis.setState. We need to replace various functionalities associated withthis with alternative tools and the transformation relies on the fact that the value ofthis is stable all across the class lifecycle.

About

say goodbye to class components

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors2

  •  
  •  

[8]ページ先頭

©2009-2025 Movatter.jp