Unification layer between React
, Preact
and Vue3
. Write React-like unified components and use everywhere without recompilation.
React
as well as Vue3
comes with Virtual DOM
and JSX
approaches.
There are a few differences at the low level.
This nano-library negates the differences between them on JSX
level.
Making it possible to implement universal components that work without recompiling in both React
and Vue3
.
Also the following React-like core hooks are available for Vue3
:
useState
useEffect
useLayoutEffect
useCallback
useMemo
useRef
$ npm i --save @redneckz/uni-jsx
or:
$ yarn add @redneckz/uni-jsx
React 17
introduced new JSX transform with separate runtime react/jsx-runtime
which can be replaced with the custom one.
For details please check this out https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
To transpile universal components configure TypeScript
as follows:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@redneckz/uni-jsx"
// ...
}
// ...
}
Universal component:
import { JSX } from '@redneckz/uni-jsx';
import { useState, useCallback, useRef, useEffect } from '@redneckz/uni-jsx/lib/hooks';
export interface TextBlockProps {
html?: string;
onClick?: (event: Event) => void;
}
export const TextBlock = JSX<TextBlockProps>(props => {
const { html, onClick, children } = props;
const [isDark, setDark] = useState(false);
const handleClick = useCallback(
(event: Event) => {
setDark(_ => !_);
onClick && onClick(event);
},
[onClick]
);
const ref = useRef<HTMLParagraphElement | null>(null);
useEffect(() => {
console.log('ref', ref.current);
}, []);
const style = isDark ? { color: '#CCC', backgroundColor: '#777' } : {};
return (
<section className="text-block" style={style} onClick={handleClick}>
{html && <p ref={ref} dangerouslySetInnerHTML={{ __html: html }}></p>}
{children && <p>{children}</p>}
</section>
);
});
import '@redneckz/uni-jsx/lib/setup.vue';
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
<template>
<h1>Vue with unified components</h1>
<text-block html="<em>Primary text...</em>" @click="debugEvent">
<cite>http://www.asimovonline.com</cite>
</text-block>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { TextBlock } from '@demo/ui-kit';
export default defineComponent({
name: 'App',
components: {
TextBlock
},
methods: {
debugEvent(event: Event) {
console.log(event);
}
}
});
</script>
import '@redneckz/uni-jsx/lib/setup.react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
<script>
globalThis.__UNI_REACT__ = true; // Temporal requirement
</script>
</html>
import { useCallback } from 'react';
import { TextBlock } from '@demo/ui-kit';
export function App() {
const debugEvent = useCallback((_: unknown) => {
console.log(_);
}, []);
return (
<>
<h1>React with unified components</h1>
<TextBlock html="<em>Primary text...</em>" onClick={debugEvent}>
<cite>http://www.asimovonline.com</cite>
</TextBlock>
</>
);
}
Please take a look at /demo/next-demo
Please take a look at /demo/nuxt-demo
Please take a look at /demo/preact-demo
package.json:
{
// ...
"dependencies": {
"next": "^12.3.3", // Not works with Next.js 13
"preact": "^10.11.3",
"preact-render-to-string": "^5.2.6",
"react": "npm:@preact/compat@^17.1.2",
"react-dom": "npm:@preact/compat@^17.1.2",
"react-ssr-prepass": "npm:preact-ssr-prepass@^1.2.0"
}
// ...
}
next.config.js
const withPreact = require('next-plugin-preact');
module.exports = withPreact({});
setup.ts
import { setupHooks } from '@redneckz/uni-jsx/lib/hooks/index';
import { setup } from '@redneckz/uni-jsx/lib/setup';
import { h } from 'preact';
import * as React from 'react';
globalThis.__UNI_REACT__ = true;
setup((type, rawProps) => {
const { children, ...props } = rawProps;
return h(type, props, children);
});
setupHooks(React);
-
useBool
- boolean state with operations
const [isEnabled, { setTrue: enable, setFalse: disable, toggle }] = useBool();
-
useList
- list with operations
const [items, { push: addItem, pop: removeLastItem, remove: removeItem, clear: removeAllItems }] = useList<BuzItem>();
-
useDict
- dictionary with operations
const [
form,
{
setDict: updateForm,
setItem: updateField,
removeItem: removeField,
assign: updateFields,
unassign: removeFields,
clear: resetForm
}
] = useDict<BuzForm>({});
-
useAsyncData
-useSWR
-like hook
const fetcher = useCallback(
async (url: string) => {
await delay(timeout);
return fetchJSON<ChuckJoke>(url);
},
[timeout]
);
const { data: chuckJoke } = useAsyncData<ChuckJoke>(
`https://api.chucknorris.io/jokes/random?category=${CATEGORIES[rnd % CATEGORIES.length]}`,
fetcher
);
-
useEventListener
- apply event handler to native DOM node
useEventListener(globalThis.document, 'click', handleDocumentClick);
-
useOutsideClick
- registers callback to handle click outside of some node (menu, dropdown, dialog and so on)
const menuRef = useOutsideClick<HTMLDivElement>(closeDropdownMenu);
...
<div role="menu" ref={menuRef}>...</div>
-
useDebouncedEffect
- debounced effect with default timeout - 300ms
useDebouncedEffect(
() => {
doSomethingWith(some, dependencies);
},
[some, dependencies],
300
);
-
useScript
- TODO
// TODO
Common limitations:
- Events are streamed up as-is (avoid usage of normalized Event fields specific to
React
orVue
)
React specific limitations:
- NO refs forwarding