Why React Hooks appear
import { Request } from 'express';
import { Controller, Get, Post } from './core';
@Controller('/test')
export class TestController {
@Get()
async getAll() {
return 'TEST !!!';
}
@Get('/a')
async getA() {
return 'WTF A';
}
@Post('/b')
async setB(req: Request) {
return `Request Body is ${JSON.stringify(req.body)}`;
}
}
Reflect & Decorator
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'bar',
templateUrl: './bar.component.html',
styleUrls: ['./bar.component.css']
})
export class BarComponent implements OnInit {
@Input() bar: string
constructor() { }
ngOnInit() {
}
}
Angular
import { Component, State } from 'react';
@Component()
export class CounterComponent extends Component {
@State() count: number = 1;
addOne = () => {
this.count += 1;
}
render() {
return (
<div>
count: {this.count}
<button onClick={this.addOne}>add one</button>
</div>
);
}
}
At the begining
React want class component to be
<template>
<div>
<button v-on:click="decrement">-</button>
{{ count }}
<button v-on:click="increment">+</button>
</div>
</template>
<script>
import 'reflect-metadata';
import { Vue, Component, Prop } from 'vue-property-decorator';
@Component
export default class Counter extends Vue {
@Prop() initialCount: number;
count = this.initialCount;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}
</script>
Vue 2 decorator support
export interface CounterProps {
initialCount: number;
}
@Hookable()
class Counter extends HookableComponent<CounterProps> {
@State() count = this.props.initialCount;
addOne = () => {
this.count += 1;
}
render() {
return (
<div>
<p>Count: {this.count}</p>
<p>Initial count: {this.props.initialCount}</p>
<button onClick={this.addOne}>Increase</button>
</div>
);
}
}
Example - for fun
import "reflect-metadata";
const HOOK_METADATA = "__hooks__";
export abstract class HookableComponent<P = {}> {
constructor(public props: P) {}
abstract render(): JSX.Element;
}
type HookableHook = (instance: HookableComponent) => void;
const defineHookMetadata = (target: Object, hook: HookableHook) => {
const hooks = Reflect.getMetadata(HOOK_METADATA, target) || [];
hooks.push(hook);
Reflect.defineMetadata(HOOK_METADATA, hooks, target);
};
export const State = (): PropertyDecorator => (target, propertyKey) => {
const hook: HookableHook = instance => {
const initialValue = instance[propertyKey as keyof typeof instance];
const [value, setvalue] = useState(initialValue);
Reflect.defineProperty(instance, propertyKey, {
get: () => value,
set: setvalue
});
};
defineHookMetadata(target, hook);
};
export const Hookable = (): ClassDecorator => target => {
function HookableComponent(props: any) {
const ref = useRef<HookableComponent>();
if (!ref.current) {
ref.current = Reflect.construct(target, [props]);
}
const instance = ref.current!;
instance.props = props;
const hooks: HookableHook[] = Reflect.getMetadata(HOOK_METADATA, instance);
hooks.forEach(hook => hook(instance));
return instance.render();
}
HookableComponent.name = target.name;
return HookableComponent as any;
};
But
React & Vue(v3)
Decide to use composition function api
Why they deprecate using class based component api ?
Class in JavaScript
You have to understand how this works in JavaScript, which is very different from how it works in most languages
Without unstable syntax proposals, the code is very verbose.
class SomeComponent extends React.Component {
constructor() {
this.onClick = this.onClick.bind(this);
}
onClick() {
// ...
}
}
class SomeComponent extends React.Component {
onClick= () => {
// ...
}
}
Decorator proposal
Still on stage 2 (Draft)
TypeScript Support
interface Theme {
// ...
}
const ThemeContext = createContext<Theme>(defaultTheme);
@Hookable()
class SomeComponent extends HookableComponent<CounterProps> {
@Context(ThemeContext) theme: Theme;
// ...
}
const SomeComponent: FC = () => {
const theme = useContext(ThemeContext);
};
Type Inference
Reuse logic
@Hookable()
class CounterMixin extends HookableMixin<CounterProps> {
@State() count = this.props.initialCount;
addOne() {
this.count++;
}
}
@Hookable()
class TwoCounter extends Mixins(CounterMixin, CounterMixin) {
// ...
}
Naming Conflict
function createCouterMixin(config) {
@Hookable()
class CounterMixin extends HookableMixin<CounterProps> {
@State() count = this.props.initialCount;
addOne() {
this.count++;
}
}
return CounterMixin;
}
@Hookable()
class TwoCounter extends Mixins(CounterMixin(/* ... */), CounterMixin(/* ... */) {
// ...
}
Naming Conflict
function useCounter(initialCount: number) {
return useState(initialCount);
}
const TwoCounter: FC = props => {
const [count1, setCount1] = useState(props.initialCount1);
const [count2, setCount2] = useState(props.initialCount2);
};
Composition
Appendix
So why Angular still use decorator and class ?
AOT
ahead of time compilation
Transpile + PreCompile
(function () {
function fibonacci(x) {
return x <= 1 ? x : fibonacci(x - 1) + fibonacci(x - 2);
}
window.x = fibonacci(15);
})();
window.x = 610;
Pre Compile
(function () {
var self = this;
['A', 'B', 42].forEach(function(x) {
var name = '_' + x.toString()[0].toLowerCase();
var y = parseInt(x);
self[name] = y ? y : x;
});
})();
_a = "A";
_b = "B";
_4 = 42;
Common Transpilers
In Ecosystem of ECMAScript
const [a,,b] = [1, 2, 3];
"use strict";
var _ref = [1, 2, 3],
a = _ref[0],
b = _ref[2];
const args = [1, 2, 3];
const foo = (...nums) => nums.reduce((acc, num) => acc + num, 0);
foo(...x);
"use strict";
var args = [1, 2, 3];
var foo = function foo() {
for (var _len = arguments.length, nums = new Array(_len), _key = 0; _key < _len; _key++) {
nums[_key] = arguments[_key];
}
return nums.reduce(function (acc, num) {
return acc + num;
}, 0);
};
foo.apply(void 0, x);
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>
Clicked {count} {count === 1 ? 'time' : 'times'}
</button>
Svelte
/* App.svelte generated by Svelte v3.19.2 */
import {
SvelteComponent,
append,
detach,
element,
init,
insert,
listen,
noop,
safe_not_equal,
set_data,
space,
text
} from "svelte/internal";
function create_fragment(ctx) {
let button;
let t0;
let t1;
let t2;
let t3_value = (/*count*/ ctx[0] === 1 ? "time" : "times") + "";
let t3;
let dispose;
return {
c() {
button = element("button");
t0 = text("Clicked ");
t1 = text(/*count*/ ctx[0]);
t2 = space();
t3 = text(t3_value);
},
m(target, anchor) {
insert(target, button, anchor);
append(button, t0);
append(button, t1);
append(button, t2);
append(button, t3);
dispose = listen(button, "click", /*handleClick*/ ctx[1]);
},
p(ctx, [dirty]) {
if (dirty & /*count*/ 1) set_data(t1, /*count*/ ctx[0]);
if (dirty & /*count*/ 1 && t3_value !== (t3_value = (/*count*/ ctx[0] === 1 ? "time" : "times") + "")) set_data(t3, t3_value);
},
i: noop,
o: noop,
d(detaching) {
if (detaching) detach(button);
dispose();
}
};
}
function instance($$self, $$props, $$invalidate) {
let count = 0;
function handleClick() {
$$invalidate(0, count += 1);
}
return [count, handleClick];
}
class App extends SvelteComponent {
constructor(options) {
super();
init(this, options, instance, create_fragment, safe_not_equal, {});
}
}
export default App;

import { Component } from '@angular/core';
@Component({
selector: 'my-component',
template: '{{person.addresss.street}}'
})
class MyComponent {
person?: Person;
}
??
React
However, we found that class components can encourage unintentional patterns that make these optimizations fall back to a slower path. Classes present issues for today’s tools, too.
import { Component } from '@angular/core';
@Component({
selector: 'my-component',
template: '{{person.addresss.street}}'
})
class MyComponent {
person?: Person;
}
my.component.ts.MyComponent.html(1,1): : Property 'addresss' does not exist on type 'Person'. Did you mean 'address'?
Template Type Check
@Injectable()
class CounterService {
readonly count$ = new BehaviorSubject(0);
increment(num: number) {
this.count.next(this.count.value + number);
}
}
@Component({
selector: 'counter',
template: `
<button (click)="counter.increment(1)">add one</button>
<span>{{ counter.count$ | async }}</span>
`
})
class TwoCounterComponent {
count: number;
constructor(
public readonly counter: CounterSerivce
) {}
}
Services w/ DI container
Roast
import React from 'react';
const ThemeContext = createContext();
const AuthContext = createContext();
const MyContext = createContext();
<ThemeContext.Provider>
<AuthContext.Provider>
<MyContext.Provider>
// ...
Where is useProvide ?

Q & A
Why React Hooks appear
By jjaayy
Why React Hooks appear
- 427