React on ES6+:react.component vs react.createclass的异同
Author:[email protected] Date:
之前react 代码是这样的:
var ExampleComponent = React.createClass({ propTypes: { aStringProp: React.PropTypes.string }, getDefaultProps: function() { return { aStringProp: '' }; } render() { return ( <div></div> ); } });
但是到了es6时代
Reac on ES6+
that's from:https://babeljs.io/blog/2015/06/07/react-on-es6-plus
While redesigning Instagram Web from the inside out this year, we enjoyed using a number of ES6+ features to write our React components. Allow me to highlight some of the ways that these new language features can change the way you write a React app, making it easier and more fun than ever.
Classes
By far the most outwardly visible change to how we write React components using ES6+ comes about when we choose to use the class definition syntax. Instead of using the React.createClass method to define a component, we can define a bonafide ES6 class that extendsReact.Component:
class Photo extends React.Component { render() { return <img alt={this.props.caption} src={this.props.src} />; } }
Right away, you’ll notice a subtle difference – a more terse syntax is available to you when defining classes:
// The ES5 wayvar Photo = React.createClass({ handleDoubleTap: function(e) { … }, render: function() { … },});
// The ES6+ wayclass Photo extends React.Component { handleDoubleTap(e) { … } render() { … }}
Notably, we’ve dropped two parentheses and a trailing semicolon, and for each method declared we omit a colon, a function keyword, and a comma.
All of the lifecycle methods but one can be defined as you would expect when using the new class syntax. The class’ constructor now assumes the role previously filled by componentWillMount:
// The ES5 wayvar EmbedModal = React.createClass({ componentWillMount: function() { … },});
// The ES6+ wayclass EmbedModal extends React.Component { constructor(props) { super(props); // Operations usually carried out in componentWillMount go here }}
Property initializers
In the ES6+ class world, prop types and defaults live as static properties on the class itself. These, as well as the component’s initial state, can be defined using ES7 property initializers:
// The ES5 wayvar Video = React.createClass({ getDefaultProps: function() { return { autoPlay: false, maxLoops: 10, }; }, getInitialState: function() { return { loopsRemaining: this.props.maxLoops, }; }, propTypes: { autoPlay: React.PropTypes.bool.isRequired, maxLoops: React.PropTypes.number.isRequired, posterFrameSrc: React.PropTypes.string.isRequired, videoSrc: React.PropTypes.string.isRequired, },});
// The ES6+ wayclass Video extends React.Component { static defaultProps = { autoPlay: false, maxLoops: 10, } static propTypes = { autoPlay: React.PropTypes.bool.isRequired, maxLoops: React.PropTypes.number.isRequired, posterFrameSrc: React.PropTypes.string.isRequired, videoSrc: React.PropTypes.string.isRequired, } state = { loopsRemaining: this.props.maxLoops, }}
ES7 property initializers operate inside the class’ constructor, where thisrefers to the instance of the class under construction, so the initial state can still be made to depend on this.props. Notably, we no longer have to define prop defaults and the initial state object in terms of a getter function.
Arrow functions
The React.createClass method used to perform some extra binding work on your component’s instance methods to make sure that, inside them, thethis keyword would refer to the instance of the component in question.
// Autobinding, brought to you by React.createClassvar PostInfo = React.createClass({ handleOptionsButtonClick: function(e) { // Here, 'this' refers to the component instance. this.setState({showOptionsModal: true}); },});
Since we don’t involve the React.createClass method when we define components using the ES6+ class syntax, it would seem that we need to manually bind instance methods wherever we want this behavior:
// Manually bind, wherever you need toclass PostInfo extends React.Component { constructor(props) { super(props); // Manually bind this method to the component instance... this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this); } handleOptionsButtonClick(e) { // ...to ensure that 'this' refers to the component instance here. this.setState({showOptionsModal: true}); }}
Luckily, by combining two ES6+ features – arrow functions and property initializers – opt-in binding to the component instance becomes a breeze:
class PostInfo extends React.Component { handleOptionsButtonClick = (e) => { this.setState({showOptionsModal: true}); }}
The body of ES6 arrow functions share the same lexical this as the code that surrounds them, which gets us the desired result because of the way that ES7 property initializers are scoped. Peek under the hood to see why this works.
Dynamic property names & template strings
One of the enhancements to object literals includes the ability to assign to a derived property name. We might have originally done something like this to set a piece of state:
var Form = React.createClass({ onChange: function(inputName, e) { var stateToSet = {}; stateToSet[inputName + 'Value'] = e.target.value; this.setState(stateToSet); },});
Now, we have the ability to construct objects whose property names are determined by a JavaScript expression at runtime. Here, we use atemplate string to determine which property to set on state:
class Form extends React.Component { onChange(inputName, e) { this.setState({ [`${inputName}Value`]: e.target.value, }); }}
Destructuring & spread attributes
Often when composing components, we might want to pass down most of a parent component’s props to a child component, but not all of them. In combining ES6+ destructuring with JSX spread attributes, this becomes possible without ceremony:
class AutoloadingPostsGrid extends React.Component { render() { var { className, ...others, // contains all properties of this.props except for className } = this.props; return ( <div className={className}> <PostsGrid {...others} /> <button onClick={this.handleLoadMoreClick}>Load more</button> </div> ); }}
We can combine JSX spread attributes with regular attributes too, taking advantage of a simple precedence rule to implement overrides and defaults. This element will acquire the className “override” even if there exists a className property in this.props:
<div {...this.props} className="override"> …</div>
This element will regularly have the className “base” unless there exists a className property in this.props to override it:
<div className="base" {...this.props}> …</div>
First, let’s explore the syntax differences by looking at two code examples and annotating them.
React.createClass versus extends React.Component
React.createClass
Here we have a const with a React class assigned, with the important render function following on to complete a typical base component definition.
import React from 'react'; const Contacts = React.createClass({ render() { return ( ); } }); export default Contacts;
React.Component
Let’s take the above React.createClass definition and convert it to use an ES6 class.
import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); } render() { return ( ); } } export default Contacts;
From a JavaScript perspective we’re now using ES6 classes, typically this would be used with something like Babel to compile the ES6 to ES5 to work in other browsers. With this change, we introduce the constructor, where we need to call super() to pass the props to React.Component.
For the React changes, we now create a class called “Contacts” and extend fromReact.Component instead of accessing React.createClass directly, which uses less React boilerplate and more JavaScript. This is an important change to note further changes this syntax swap brings.
propTypes and getDefaultProps
There are important changes in how we use and declare default props, their types and setting initial states, let’s take a look.
React.createClass
import React from 'react'; const Contacts = React.createClass({ propTypes: { }, getDefaultProps() { return { }; }, render() { return ( ); } }); export default Contacts;
React.Component
This uses propTypes as a property on the actual Contacts class instead of a property as part of the createClass definition Object. I think it’s nicer syntax to create class properties so it’s much clearer what are React APIs versus your own on the definition Object.
The getDefaultProps has now changed to just an Object property on the class calleddefaultProps, as it’s no longer a “get” function, it’s just an Object. I like this syntax as it avoids more React boilerplate, just plain JavaScript.
import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); } render() { return ( ); } } Contacts.propTypes = { }; Contacts.defaultProps = { }; export default Contacts;
State differences
State is an interesting change, now we’re using constructors the implementation of initial states changes.
React.createClass
We have a getInitialState
function, which simply returns an Object of initial states.
import React from 'react'; const Contacts = React.createClass({ getInitialState () { return { }; }, render() { return ( ); } }); export default Contacts;
React.Component
The getInitialState
function is deceased, and now we declare all state as a simple initialisation property in the constructor
, which I think is much more JavaScript-like and less “API” driven.
import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); this.state = { }; } render() { return ( ); } } export default Contacts;
“this” differences
Using React.createClass
will automatically bind this
values correctly for us, but changes when using ES6 classes affect this.
React.createClass
Note the onClick
declaration with this.handleClick
bound. When this method gets called React will apply the right execution context to handleClick
.
import React from 'react'; const Contacts = React.createClass({ handleClick() { console.log(this); // React Component instance }, render() { return ( ); } }); export default Contacts;
React.Component
With ES6 classes this is slightly different, properties of the class do not automatically bind to the React class instance.
import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); } handleClick() { console.log(this); // null } render() { return ( ); } } export default Contacts;
There are a few ways we could bind the right context, here’s how we could bind inline:
import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); } handleClick() { console.log(this); // React Component instance } render() { return ( ); } } export default Contacts;
Alternatively we could change the context of this.handleClick inside the constructorto avoid inline repetition, which may be a better approach if moving to this syntax to avoid touching JSX at all:
import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { console.log(this); // React Component instance } render() { return ( ); } } export default Contacts;
Mixins
React mixins are no longer supported when using React components written in ES6.
React.createClass
With React.createClass we can add mixins to components using a mixins property which takes an Array of available mixins. These then extend the component class.
import React from 'react'; var SomeMixin = { doSomething() { } }; const Contacts = React.createClass({ mixins: [SomeMixin], handleClick() { this.doSomething(); // use mixin }, render() { return ( ); } }); export default Contacts;
React.Component
Mixins aren’t supported in ES6 classes.
Recommendations
Facebook does suggest the future removal of React.createClass completely in favour of ES6 classes. For now, use what makes sense, they’re both just syntax with different semantics that do the same thing - they’re both classes!
下面是对比总结文章
react.component vs react.createclass
Step 1 - Extract `propTypes` and `getDefaultTypes` to properties on the component constructor
Unlike object literals, which the `createClass` API expected, class definitions in ES6 only allow you to define methods and not properties. The committee's rationale for this was primarily to have a minimal starting point for classes which could be easily agreed upon and expanded in ES7. So for class properties, like `propTypes`, we must define them outside of the class definition.
Another change in React's 0.13 release is that `props` are required to be immutable. This being the case, `getDefaultProps` no longer makes sense as a function and should be refactored out to a property on the constructor, as well.
//现在官方建议: import React from 'react'; class Contacts extends React.Component { constructor(props) { super(props); } render() { return ( <div></div> ); } } Contacts.propTypes = { aStringProp: React.PropTypes.string }; Contacts.defaultProps = { }; export default Contacts;
Step 2 - Convert component from using `createClass` to being an ES6 Class
ES6 class bodies are more terse than traditional object literals. Methods do not require a `function` keyword and no commas are needed to separate them. This refactoring looks as such:
var ExampleComponent = React.createClass({ render: function() { return Hello, world.; }, _handleClick: function() { console.log(this); } }); class ExampleComponent extends React.Component { render() { return Hello, world.; } _handleClick() { console.log(this); } }
Step 3 - Bind instance methods / callbacks to the instance
One of the niceties provided by React's `createClass` functionality was that it automatically bound your methods to a component instance. For example, this meant that within a click callback `this` would be bound to the component. With the move to ES6 classes, we must handle this binding ourselves. The React team recommends prebinding in the constructor. This is a stopgap until ES7 allows property initializers.
class ExampleComponent extends React.Component { render() { return Hello, world.; } _handleClick() { console.log(this); // this is undefined } } //before class ExampleComponent extends React.Component { constructor() { super(); this. _handleClick = this. _handleClick.bind(this); } render() { return Hello, world.; } _handleClick() { console.log(this); // this is an ExampleComponent } }
Step 4 - Move state initialization into the constructor
The React team decided a more idiomatic way of initializing state was simply to store it in an instance variable setup in the constructor. This means you can refactor away your `getInitialState` method by moving its return value to be assigned to the `this.state` instance variable in your class' constructor.
class ExampleComponent extends React.Component { getInitialState() { return Store.getState(); } constructor() { super(); this. _handleClick = this. _handleClick.bind(this); } // ... } After: class ExampleComponent extends React.Component { constructor() { super(); this. _handleClick = this. _handleClick.bind(this); this.state = Store.getState(); } // ... }
Conclusion
The handful of refactoring steps needed to convert an existing component to an ES6 class / React 0.13 and beyond component is pretty straightforward. While `React.createClass` is not deprecated, and will not be until JavaScript has a story for mixins, there is a strong consensus that working in the direction the language is heading is wise.
As a closing thought, consider one additional refactoring that introduces your project's own base Component class to hold niceties that are reused through your own Component library.
Bonus Step - Refactor to a base component
class ExampleComponent extends React.Component { constructor() { super(); this. _handleClick = this. _handleClick.bind(this); this. _handleFoo = this. _handleFoo.bind(this); } // ... } //after class BaseComponent extends React.Component { _bind(...methods) { methods.forEach( (method) => this[method] = this[method].bind(this) ); } } class ExampleComponent extends BaseComponent { constructor() { super(); this._bind('_handleClick', '_handleFoo'); } // ... }
react.component vs react.createclass 那个威武?
我也不懂!然后去官网查 了下……仙人个板板,看不懂哈
React has supported building components two different ways for a few months. You can extend from React.Component or use React.createClass which has been available since the beginning of React. Is there a good reason to use one over the other?
Maybe, maybe not. That’s up to you.
或许把?为何??
Here’s my take: In the large scheme of things, it doesn’t matter that much. For most of the cases out there, the difference between React.createClassand class X extends React.component is that of syntax. If you don’t use mixins or decorators often, just choose the syntax you like the best.
But apart from that, there are some real reasons to choose one way over the other.
There are some real features you lose by going with ES6 Classes (I’m not going to say ES2015, you can’t make me!) — namely mixins, autoBound functions and the oft-forgotten this.isMounted method. ES6 classes also means you now have a hard dependency on a tool like Babel. If you’ve not embraced JSX, and are currently writing ES5 code that doesn’t need transpilation, this might be a dealbreaker for you.
But before we get into the pros and cons list, let say something that people tend to overlook. Using ES6 classes instead of React.createClass DOES NOT make your code any more or less Object oriented. It’s just a different syntax for defining classes folks, it has a fewer features, but essentially you’re moving from a factory pattern to a constructor pattern. So, if you like your code nice and functional, this should be a non-debate for you.
On the flip side, using ES6 classes does make it easier to do inheritance. But please, don’t. Let me put it this way, if you’re going to use ES6 classes just so you can make deep inheritance chains, just stick to React.createClass and write some mixins.
Reasons to use React.createClass
“I like auto-binding functions”
This is a valid argument, except you can autobind with ES2015 classes, (See the React blog post)
Using Babel stage: 0 (which I’m personally a huge fan of) you can write your classes like this:
class Counter extends React.Component { tick = () => { ... } ... }
If you think stage: 0 is way too extreme, there are other options out there. You can, for example, use an autobind decorator:
But decorators are a stage: 0 feature, I hear you say. Yes, but you don’t need stage: 0:
class Counter extends React.Component { tick() { ... } render(){ ... } ... } export default autobind(Counter)
“I like mixins”
This is pretty much the main reason people are sticking to React.createClass, and for good reason. There are large React code bases that rely on mixins. React-router, for example, gets a lot of power by using mixins. Again, you can use React-mixin, to use mixins with ES6 classes, but you may be getting annoyed by the decorators by now.
Little things like this.isMounted
You hardly ever need to use them, and when you do, they are easy to add. Personally I find no reason for using this.isMounted in your code.
Reasons to switch to the ES6 syntax
Autobinding?
Maybe this is stockholme syndrome, but we’ve been dealing with context issues in Javascript so long, that it’s starting to feel right. The automatic autobinding that React.createClass handles for you can be confusing to beginners, and the implicit nature of the binding can be confusing even after months for some. ES6 classes make you explicitly bind your methods. Which makes everything clearer, and will help developers new to React grok what’s going on. With some of the latest Babel-supported ES6/7 features, manual binding isn’t much of a problem.
Move over Mixins, use Higher-Order-Components
Go to any conversation about ES6 classes, and you’ll find someone telling you to use composition over inheritance. You may have seen this meme before:
The fact is that inheritance is a terrible way to code. It’s error-prone, clunky and hard to understand. It can lead to extremely brittle code, and forces you to write all your code the same way. Mixins are definitely a much better solution, but developers still tend to abuse them to do things that could simply be done with composition. Who said you can’t be functional with classes? As an added bonus, Higher-Order-Component will work with both kinds of classes, and will be forward compatible with pure functions.
On the other hand, using decorator functions, you can do some very powerful things with ES6 classes, such as polyfill the oft-discussed polyfill API. This power should be used sparingly, but when you do need it, it’s nice to have.
No Cruft
Getting rid of features such as this.isMounted which is rarely used in practice helps React be lighter and more nimble. Over time this is also helping React be faster. I know we all love React, but we also want to keep winning the speed tests, don’t we.
FlowTypes
This is one is near and dear to my heart. For a very long time, I’ve pretty much ignored Typescript and Flow, but after losing a whole day to a typo in an event name, I started using flow in my code and I haven’t looked back. Flow lets you embrace it slowly on a file-by-file basis, and even though it may make you jump through hoops sometimes to work around errors, it will find a whole bunch of subtle errors that you didn’t even know existed.
But what does this have anything to do with ES6 class syntax? Flowtype (and typescript) are much easier to use if you’re using ES6 classes.
This is how you can annotate properties in an ES6 class:
Class X extends React.Component { someProp: string | number; state: SomeType; props: SomeType; ... }
The same is a little more complicated with React.createClass ~~~js React.createClass({ someProp: (0: string | number), … }) ~~~
You can’t even define types for props and state with flow with React.createClass. Instead, flow depends on a huge amount of custom code to figure out the types for props by looking at propTypes. In practice, it never works that well. And type checking state is simply not even possible.
Conclusion
Neither of these options for creating your classes are going away anytime soon. I feel that things are headed towards the ES6 way of doing things but it will be a while until it’s mainstream. If it ever becomes something everyone chooses over createClass, Javascript needs more than just syntactic sugar, it needs real classes. I choose to write my components the ES6 way mainly because I feel that it looks a little nicer, no commas after every function and the downsides to using this syntax doesn’t bother me that much. We would love to hear feedback in the comments about what you think! Hopefully we’ll discuss this on the next episode of the React Podcast.
转载本站文章《React on ES6+:react.component vs react.createclass的异同》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/jsBase/2016_0518_7831.html
延伸阅读:
- grunt使用yeoman自动化构建react项目……
- browser.js什么鬼?作用是什么
- React项目中常见的技术坑与优化及Component Generator
- react更新组件componentWillReceiveProp里面setState无效,未触发渲染
- can't resolve 'redux-thunk' in *** 项目不能跑起
- React+redux组件最简单的计算器!
- react+redux渲染页面空白,原来是大小写惹的祸害
- react:Uncaught TypeError: Cannot read property
- react组件中bind(this)写在哪里好?
- react-dom.min.js:15 Uncaught (in promise) Error: Minified React error
- react hook context 管理全局状态
- TypeScript写React组件默认属性问题
- React Query与SWR尚能饭否?React Query还真香!
- React 同构实践与思考
- React代数效应学习笔记
- ReactHook详解:memo/useMemo/useCallback等钩子细讲
- jsx动态class写法:vue3与react+classname库
- react异步数据如ajax请求应该放在哪个生命周期?
- React 源码剖析系列—生命周期的管理艺术—有限状态机
- React 源码剖析系列 - 解密 setState
- React16源码分析(2):react.development.js源码注释
- React16源码分析(1):react项目架构/文件目录/包结构解读
- React16源码分析(0):对象池/合成事件/事务机制等概念科普
- React Fiber实现原理分析
- 再谈react优势——react技术栈回顾