Skip to content

Instantly share code, notes, and snippets.

@barelyhuman
Created August 13, 2023 11:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barelyhuman/e22959ebd2da050e7e9f060763cbe7ee to your computer and use it in GitHub Desktop.
Save barelyhuman/e22959ebd2da050e7e9f060763cbe7ee to your computer and use it in GitHub Desktop.
Wrapper around react to write Vue options like components

It's loosely inspired by Vue options and takes the separation of concerns a bit more seriously. It is decently typed to satify the basic DX, if you've got improvements, please let me know.

Usage

The snippet depends on

  • lodash.isequal
  • @vue/reactivity
npm i lodash.isequal @vue/reactivity

Preferred Syntax

  • data at the top
  • onMount,onDestroy next
  • methods after that
  • render at the last

They are all just functions, no magic, just hidden reactivity

import { Apex as $ } from "@/lib/ApexRenderer";

const Counter = $({
  data() {
    return {
      count: 0,
    };
  },
  methods() {
    return {
      inc: () => {
        return this.$data.count++;
      },
      incAsync: async () => {
        return this.$data.count++;
      },
    };
  },
  render() {
    return (
      <>
        <button onClick={this.$methods.inc}>{this.$data.count}</button>
      </>
    );
  },
});
import React, { Component } from "react";
import { effect, reactive } from "@vue/reactivity";
import isEqual from "lodash.isequal";
type Props = {
props: any;
};
type MethodThis<D> = {
$data?: D;
};
type RenderThis<M, D> = {
$data?: D;
$methods?: M;
};
type Config<M, D, P> = {
globalMethods?: () => any;
globalData?: () => any;
methods?: (this: MethodThis<D>) => M;
data?: () => D;
onMount?: (o: Props) => void;
onDestroy?: () => void;
$data?: D;
$methods?: M;
$globalMethods?: any;
$globalData?: any;
render: (
this: RenderThis<M, D>,
opt: RenderOptions<M, D, P>
) => React.ReactNode;
};
type RenderOptions<M, D, P> = {
config: Config<M, D, P>;
props: P;
};
let globalMethods = {};
let globalData = reactive({});
export function Apex<M, D, P>(config: Config<M, D, P>) {
return class EffectRenderer extends Component<P> {
tree: null | React.ReactNode;
$data: D;
$methods: M;
constructor(props) {
super(props);
this.tree = null;
this.makeDataReactive();
}
componentDidMount() {
this.run();
}
componentDidUpdate(prevProps: Readonly<{}>): void {
if (isEqual(prevProps, this.props)) {
return;
}
// re-create tree if props have changed
this._createTree();
}
componentWillUnmount() {
config.onDestroy && config.onDestroy();
this.tree = null;
}
makeDataReactive() {
const _data = config.data ? config.data() : {};
if (_data instanceof Promise) {
throw new Error(
"`data` needs to be a sync method, if you have data that might come from network, please inject it `onMount` or by triggering a possible `$method` "
);
}
this.$data = <D>reactive(_data);
}
async run() {
var self = this;
this.$methods = <M>(config.methods ? config.methods() : {});
Object.keys(this.$methods).forEach((key) => {
self.$methods[key] = self.$methods[key].bind(config);
});
const newGlobalMethods =
(config.globalMethods && config.globalMethods()) || {};
Object.keys(newGlobalMethods).forEach((key) => {
if (globalMethods[key]) {
console.warn(`Overlapping key:${key} on global methods`);
}
globalMethods[key] = newGlobalMethods[key].bind({
$globalData: globalData,
});
});
const newGlobalData = (config.globalData && config.globalData()) || {};
Object.keys(newGlobalData).forEach((key) => {
if (globalData[key]) {
console.warn(`Overlapping key:${key} on global methods`);
}
globalData[key] = newGlobalData[key];
});
Object.assign(config, {
$methods: self.$methods,
$data: self.$data,
$globalData: globalData,
$globalMethods: globalMethods,
});
if (config.onMount) {
Promise.resolve(
config.onMount({
props: self.props,
})
).then(() => {
this._createTree();
});
}
effect(() => {
console.log({ effectProps: self.props });
this._createTree();
});
}
_createTree() {
const render = config.render.bind(config);
const self = this;
this.tree = render({
config,
props: self.props,
});
this.forceUpdate();
}
render() {
return this.tree;
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment