Which ReactJS syntax to use; React.createClass or ES6 extends? - syntax

I'm beginner of ReactJS. I learned and studied a lot of documents and ebooks on various websites. I realize there are two syntaxes for ReactJS. Example:
React.createClass({
displayName: 'Counter',
getDefaultProps: function(){
return {initialCount: 0};
},
getInitialState: function() {
return {count: this.props.initialCount}
},
propTypes: {initialCount: React.PropTypes.number},
tick() {
this.setState({count: this.state.count + 1});
},
render() {
return (
<div onClick={this.tick}>
Clicks: {this.state.count}
</div>
);
}
});
And this version is written by ES6:
class Counter extends React.Component {
static propTypes = {initialCount: React.PropTypes.number};
static defaultProps = {initialCount: 0};
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
state = {count: this.props.initialCount};
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
What is the better way to use ReactJS? But I found these libraries, application on github used to perform a lot ES6.

The second approach is probably the correct one to adopt going forward as Facebook have said they will ultimately deprecate the React.createClass approach.
From the React v0.13 release notes:
Our eventual goal is for ES6 classes to replace React.createClass completely, but until we have a replacement for current mixin use cases and support for class property initializers in the language, we don't plan to deprecate React.createClass
Personally I think the second approach also makes for easier to read code, but that is obviously a more subjective reason.
However, as stated above, it's important to note that the ES6 format does not support Mixins, so if you need a mixin you need to use the createClass format for that component.
This post "React.createClass versus extends React.Component" by Todd Motto has some good information on the difference between the two syntaxes. It's worth reading that for a discussion of how the this keyword behaves differently between the two syntaxes.
Edit: Dan Caragea's post below makes some excellent points that should definitely be considered too.
A third alternative...
There is a also a third way of defining a React component, called 'Stateless Functions' in the React Documentation and often called 'Stateless Functional Components' or 'Functional Stateless Components'. This is the example from the docs:
function HelloMessage(props) {
return <div>Hello {props.name}</div>;
}
Defining the component as a function means it is effectively created anew each time and so has no ongoing internal state. This makes the component easier to to reason about, and to test, as the component's behaviour will always be identical for a given set of properties (props), rather than potentially varying from run-to-run due the values of the internal state.
This approach works particularly well when using a separate State management approach such as Redux and ensures that Redux's time-travel will produce consistent results. Functional Stateless Components also make implementing features like undo/redo simpler.

I have done React.createClass for work and ES6 classes for my pet project. I do find the latter easier to read too but I often miss the simplicity/peace of mind I have with the former.
With the class based approach, do note that, technically, the statically defined propTypes and defaultProps are ES7, not ES6 - which might change until ES7 is finalized.
A pure ES6 approach would be to declare propTypes/defaultProps like
class Counter extends React.Component {
...
}
Counter.propTypes = {...};
Counter.defaultProps = {...};
You also have to remember to bind onClick in render (or any other method where you need to use this). It's almost certain you will forget to in some places. While with createClass all calls are auto-bound by React.
Another ES7 proposal could make things easier but you'd still need to remember to write it everywhere:
<div onClick={::this.tick}> which binds this to tick.
Of course, you'd have to opt in to stage 0 in babel config to make use of all these ES7 proposals.
About mixins...there are acceptable ways of using mixins with classes. A brilliant approach is mixWith.js but you could also try ES7 decorators, HOCs, even Object.assign() :)
At the end of the day, I feel that the class approach doesn't bring anything of real value and you could go the old and paved way of createClass until you have a good understanding of React. Then you can toy around with classes and ES6/7/100. It will be a long while before they deprecate createClass.

I started with React ES6 + staging style and it sounds nice when you say in React everything is a component. I like this class organization.
Because only methods can be defined inside ES6 class if you want to define properties in pure ES6 they have to be outside of the class. Like for instance in here:
export class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
Counter.propTypes = { initialCount: React.PropTypes.number };
Counter.defaultProps = { initialCount: 0 };
This is why in ES6 propTypes section is outside of the class definition.
But if you use ES7 you can define static and non-static properties without a problem inside of the class.
// ES7
export class Counter extends React.Component {
static propTypes = { initialCount: React.PropTypes.number };
static defaultProps = { initialCount: 0 };
state = { count: this.props.initialCount };
tick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
With ES7 you can use implicit binding tips for methods also as demonstrated in here:
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
The old pre React v0.13 style or ES5 style was like this.
In React.createClass all methods are bound this automatically.
This can be a little confusing for JavaScript developers because this is not native JavaScript behavior.
This is whe write:
class Counter extends React.Component {
constructor() {
super();
this.tick = this.tick.bind(this);
}
tick() {
...
}
...
}
or use some tricks to accomplish the same using property initializer syntax.
class Counter extends React.Component {
tick = () => {
...
}
...
}
Conclude
For the newcomers, I think the latest style is a better choice.
About Mixins
As stated here:
ES6 launched without any mixin support. Therefore, there is no support for mixins when you use React with ES6 classes.
We also found numerous issues in codebases using mixins, and don't recommend using them in the new code.
This section exists only for the reference.

Related

How to build a Model Layer in Vue3 just like other MVC language?

my name is DP, I have 2 years Vue2 experience, but I am new to Vue3. I am learning Vue3 recently, as I found the "setup(Composition API)" just like the "Controller(in MVC)" that I did in other language, so I am trying to build my test Vue3 project in MVC way, but I go some problem can anyone help? thx!
MVC Plan
M - use class
V - use <template> ... </template>
C - use setup
My Problem
working: using loadTopic_inSetup().then() in setup is working, because topicList_inSetup is defined in setup() too.
not working: using loadTopic_inModel() in setup is not working, I guess some kind data keep problem, because in console I can see the data already got from API
as u can see, I am not expert for js/ts, I am a backend developer, so if you know how to do it, plz help thx very much.
BTW, VUE is greet, I love it.
My Code
//APIBased.ts
import { ajax } from "#/lib/eeAxios"
export class APIBased {
//load data with given url and params
loadData(apiPath: string, params?: object): Promise<any> {
apiPath = '/v1/'+apiPath
return ajax.get(apiPath, params)
}
}
//Topic.ts
import { APIBased } from "./APIBased";
import { ref } from 'vue'
export class Topic extends APIBased {
//try keep data in model
topicList: any = ref([]);
constructor() {
super()
}
//direct return ajax.get, let setup do the then+catch
loadTopic_inSetup() {
return super.loadData('topics', { t_type_id: 1 })
}
//run ajax get set return data to this.topicList, keep data in model
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList = re.data
})
}
}
//EETest.vue
<template>
<EELayoutMainLayout>
<template v-slot:mainContent>
<h1>{{ "Hello Vue3 !!" }}</h1>
<hr/>
{{to.topicList}} //not working... just empty array
<hr/>
{{topicList_inSetup}} //working... topic list return from API show here.
</template>
</EELayoutMainLayout>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, ref } from 'vue'
import EELayoutMainLayout from '#/components/eeLayout/EELayoutMainLayout.vue'
import { Topic } from "#/models/Topic";
export default defineComponent({
name: 'EETest',
props: {
},
setup() {
let topicList_inSetup = ref([])
const to = new Topic()
//try keep data in setup, it's working
to.loadTopic_inSetup().then((re) => {
topicList_inSetup.value = re.data
console.log(re.data)
})
//try keep data in model, the function is run, api return get, but data not show, even add ref in model
to.loadTopic_inModel()
return {
topicList,
to,
}
},
components: {
EELayoutMainLayout,
},
})
</script>
A few digressions before solving the problem. Maybe you are a java developer. I personally think it is inappropriate to write the front end with Java ideas. The design of vue3's setup is more inclined to combined functional programming
To fully understand why you need some pre knowledge, Proxy and the get and set method of Object
They correspond to the two core apis in vue, reactive and ref,
The former can only be applied to objects( because proxy can only proxy objects),The latter can be applied to any type(primary for basic javascript types, get and set can apply for any type)
You can modify the code to meet your expectations
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList.value = re.data
})
}
You cannot modify a ref object directly, a test case to explain what is reactive
when ref function is called, a will be like be wrapped in a class has value properties, and has get and set method
the effect function will call the arrow function, and in this time, the get method of a will be called and it will track as a dependence of the effect function, when a changed, the set method of a will be called, and it will trigger the arrow function,
so when you direct modify the a, the setter method will never trigger, the view will not update
const a = ref(1)
let dummy
let calls = 0
effect(() => {
calls++
dummy = a.value
})
expect(calls).toBe(1)
expect(dummy).toBe(1)
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
// same value should not trigger
a.value = 2
expect(calls).toBe(2)

Extend Observable and set external source

I want to extend Observable and set another source (e.g. another Behaviour Subject)
E.g. NgRx is doing it here:
https://github.com/ngrx/platform/blob/9.0.0/modules/store/src/store.ts#L20
But RxJS Observable gives a deprecation warning on ´Observable.source´: https://github.com/ReactiveX/rxjs/blob/6.5.5/src/internal/Observable.ts#L25
This code is using the deprecated ´Observable.source´ - and it works:
const source: BehaviorSubject<any> = new BehaviorSubject(1);
class ObsStore extends Observable<any> {
constructor() {
super();
this.source = source; // Observable.source is deprecated
}
// Implement custom methods here
customMethod1() {}
}
export const obsStore$ = new ObsStore();
obsStore$.subscribe(data => console.log('output', data));
source.next(2);
// output 1
// output 2
Still I wonder if it is safe to use source. Is there maybe an alternative way to set an external source for an Observable?
NgRx also implements the lift method:
https://github.com/ngrx/platform/blob/9.0.0/modules/store/src/store.ts#L90-L95
Not sure if that is needed.
Here is a stackblitz example:
https://stackblitz.com/edit/extend-observable
Note: I want to extend Observable and add some custom public methods to it. The Custom Observable should behave like an BehaviorSubject but it should not expose next or getValue. Actually only pipe and the custom methods should be public.
I found this tweet from Ben Lesh (RxJS):
https://twitter.com/BenLesh/status/1366787138898055169
"I strongly advise people not extend Observable (or really any type that they don't own, from any library)."
Also Alex Okrushko from the NgRx team thinks that NgRx should better not have extended Observable.
https://twitter.com/AlexOkrushko/status/1379028869789970432
Composition will work much better + future proof.

Guideline for memoized selectors in state base class

I have a question regarding NGXS Store and the usage of memoized #Selector()s in a state class hierarchy.
What would be the recommended approach to the issue described below?
The NGXS Store documentation does not provide a guideline/recommendation in this regard.
Given this sample state setup,
export interface BaseModel { data: string[]; }
// base class not a state!
export class BaseState {
#Selector()
static dataSelect(state: BaseModel) { return state.data; }
// ...
}
export interface DerivedModel extends BaseModel {}
#State<DerivedModel>({ name: 'derived'; })
export class DerivedState extends BaseState {
// ...
}
export interface UsingModel { thing: number; }
#State<UsingModel>({ name: 'using'; })
export class UsingState implements NgxsOnInit {
constructor(private _store: Store) {}
ngxsOnInit(ctx: StateContext<UsingModel>) {
// have this state update when derived state changes
this._store.select(DerivedState.dataSelect).subscribe(data => console.log(data));
}
// ...
}
When letting this piece of code run it will print out undefined because the state argument
of the dataSelect(...) method will not be set.
I tracked the cause to BaseState not being a NGXS State and therefore missing an internal NGXS_META
property which in turn causes the undefined argument.
Adding BaseState to the selector (such as #Selector([BaseState])) to force the state to still be
included also does not have the desired effect, because now NGXS cannot navigate to a matching state slice.
I found two ways to make this work as desired:
1. duplicate the #Selector(...) method in every derived state implementation. This though defeats the reasons why the state class hierarchy was originally designed.
2. use something like #DerivedSelector(...) that works according to the standard decorator but dynamically creates selectors on use for each of the encountered derived state classes.
Thank you.
As far as I know this can not be achived using the #Selector annotation, but with createSelector().
export class BaseState {
static dataSelect() {
return createSelector(
[this],
(state: BaseModel) => {
return state.data;
}
);
}
//...
}
If you change your base state like this, your code will work. For details refer to the NGXS docs

how to ignore ordering of reselect selectors when composing selectors

As I compose more selectors together, I'm finding that I'm reordering where the selectors are defined. For example,
export const selectNav = state => state.nav;
export const selectPage = state => state.page;
export const selectNavAndPage = createSelector([
selectNav,
selectPage,
], (nav, page) => {
});
export const selectFoo = state => state.foo;
export const selectNavAndPageAndFoo = createSelector([
selectNavAndPage,
selectFoo,
], (navAndPage, foo) => {
});
This is a simple example, but I could not define selectNavAndPage below selectNavAndPageAndFoo. As more selectors get composed and selectors of selectors get composed, then I need to make sure all the sub-selectors are defined at the top before I use them.
Is there some way to create these selectors such that ordering doesn't matter?
I was worried about the same problem and I created this npm module define-selectors. This is a module that delays the definition of the selector to solve the ordering of selector definition problem and adds other features to it. It has not been stable yet, but I will use it on my project to make it stable and be improved.
For more information please go to github page and read the README and source files.
I'm pretty sure this is just related to how the ES6 const keyword works. With const, variables do not exist until that line, so if you want to reference a const variable, you need to write that code after the variable declaration. With var, all variables are hosted to the top of the scope.
So, either use var so that you can reference things out of order, or continue using const and define each function in the correct order for usage and references.
If you don't mind a little extra typing, here is another approach which requires defining a 'cms' utility function that wraps the createSelector function and takes advantage of function hoisting:
import {createSelector} from 'reselect';
// create memoized selector
function cms(ctx, ...args) {
if (!ctx.selector) ctx.selector = createSelector(...args);
return ctx.selector;
}
// define the selectors out of order...
export function getBaz(state) {
return cms(
getBaz // the function itself as context
, getBar
, bar => bar.baz
)(state);
}
export function getBar(state) {
return cms(
getBar
, getFoo
, foo => foo.bar
)(state);
}
export function getFoo(state) {
return state.foo;
}
This is not as elegant as simply defining the selectors in order, but maybe someone else can take this idea and improve on it.

Aurelia 2 custom elements (already sharing a view via #) doing almost the same thing, how to refactor?

Here is my problem:
Aurelia app:
a few custom elements (already sharing a view via #UseView) doing almost the same thing (specific func shoud be defined by every element itself), how to manage shared code (inkl #bindable)?
How to refactor this:
https://gist.run/?id=897298ab1dad92fadca77f64653cf32c
The "shared" code you refer to in your question is lifecycle-related stuff in your custom elements, which isn't really suited for sharing. You would need to do inheritance, and with custom elements that's setting yourself up for a lot of headaches.
Rather than sharing code, why not focus on the things which are variable and try to make them configurable? By looking at your gist, that seems by far the most straightforward solution here.
Say you have a custom element that calls a function when a property changes. This function needs to be different for some instances of the element. You could accomplish that with a bindable function, and use the .call behavior, like so:
some-element.js
import { bindable } from 'aurelia-framework';
export class SomeElement {
#bindable value;
#bindable processValue;
valueChanged(newValue, oldValue) {
if (this.processValue) {
this.processValue({ val: newValue });
}
}
}
consumer.html
<some-element value.bind="myValue" process-value.call="myFunc(val)"></some-element>
<some-element value.bind="anotherValue" process-value.call="anotherFunc(val)"></some-element>
consumer.js
myFunc(val) {
console.log("val: " + val);
}
anotherFunc(val) {
console.log("val: " + val);
}

Resources