[🚧 Work In Progress v1.0] The fastest way to build cross-container application.
⚡ Fast: blazing fast virtual DOM.
🎯 Tiny: ~7 KB minified + gzipped.
🎨 Universal: works with DOM, Weex, Node.js, Mini-program, WebGL and could works more container that implement driver specification.
- Quick Start
- Guides
- API Reference
Quickly add rax to your project:
$ npm install rax
$ npm install driver-dom
// Hello.jsx
import {createElement, useState} from 'rax';
export default (props) => {
const [name, setName] = useState(props.name);
const handleClick = () => {
setName('rax');
};
return (
<h1 onClick={handleClick}> Hello {name}</h1>
);
}
// app.jsx
import {render} from 'rax';
import DriverDOM from 'driver-dom';
import Hello from './Hello';
render(<Hello name="world" />, document.body, { driver: DriverDOM });
Install the Rax CLI tools to init project:
$ npm install rax-cli -g
$ rax init <YourProjectName>
Start local server to launch project:
$ cd YourProjectName
$ npm run start
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/[email protected]/dist/rax.js"></script>
<script src="https://unpkg.com/[email protected]/dist/driver-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// @jsx Rax.createElement
Rax.render(
<h1>Hello, world!</h1>,
document.getElementById('root'),
{ driver: DriverDOM }
);
</script>
</body>
</html>
Use renderToString()
to generate HTML on the server and send the markup down on the initial request for faster page loads and to allow search engines to crawl your pages for SEO purposes.
// MyComponent.jsx
function MyComponent(props) {
return <h1>Hello world</h1>;
}
import express from 'express';
import renderer from 'rax-server-renderer';
import {createElement} from 'rax';
import MyComponent from './MyComponent';
const app = express();
app.listen(8080);
app.get('/hello', (req, res) => {
let html = renderer.renderToString(createElement(MyComponent));
res.send(`<!DOCTYPE html><html><body>${html}</body></html>`);
});
If you call hydrate()
on a node that already has this server-rendered markup, Rax will preserve it and only attach event handlers, allowing you to have a very performant first-load experience.
import hydrate from 'rax-hydrate';
import MyComponent from './MyComponent';
hydrate(<MyComponent />, document.body);
import { createElement, Fragment } from 'rax';
import { route, useComponent, push } from 'rax-use-router';
import Foo from './Foo';
route([{
path: '/home',
routes: [
{
path: '', // www.example.com/home
component: () => <>
<button onClick={() => push('/foo')}>go foo</button>
<button onClick={() => push('/bar')}>go bar</button>
<button onClick={() => push('/home/jack')}>go jack</button>
</>,
},
{
path: '/:username', // www.example.com/home/xxx
component: (params) => <>
<p>{params.username}</p>
<button onClick={ () => push('/home') }>Go home</button>
</>
}
]},
{
path: '/bar',
routes: [
{
path: '', // www.example.com/bar
component: () => import(/* webpackChunkName: "bar" */ './Bar'),
},
],
},
{
path: '/foo', // www.example.com/foo
component: () => <Foo />,
},
]);
export default function Example() {
var component = useComponent('/home');
return component;
}
// Foo.jsx
import { createElement } from 'rax';
import { push } from 'rax-use-router';
export default function Foo() {
return <button onClick={ () => push('/home') }>Go home</button>
}
// Bar.jsx
import { createElement } from 'rax';
import { push } from 'rax-use-router';
export default function Bar() {
return <button onClick={ () => push('/home') }>Go home</button>
}
import { createElement, useMemo } from 'rax';
import usePromise from 'rax-use-promise';
const fetchData = () => fetch('https://httpbin.org/get').then(res => res.json());
function Example() {
const [data, error] = usePromise(useMemo(fetchData));
if (error) {
return <p>error</p>
} else if (data) {
return <p>{data.foo}</p>
}
}
import { createElement } from 'rax';
import useFetch from 'rax-use-fetch';
function Example() {
const [data, error] = useFetch('https://httpbin.org/get');
if (error) {
return <p>error</p>;
} else if (data) {
return <p>{data.foo}</p>;
} else {
return <p>loading</p>;
}
}
Code-Splitting allows you to split your code into various bundles which can then be loaded on demand or in parallel. It can be used to achieve smaller bundles and control resource load prioritization which, if used correctly, can have a major impact on load time.
Code-Splitting is supported by Webpack
which can create multiple bundles that can be dynamically loaded at runtime.
import { createElement } from 'rax';
import useImport from 'rax-use-promise';
export default function App() {
const [Bar, error] = useImport(() => import(/* webpackChunkName: "bar" */ './Bar'));
if (error) {
return <p>error</p>;
} else if (Bar) {
return <Bar />
} else {
return <p>loading</p>;
}
}
rax-test-renderer
provides an renderer that can be used to render Rax components to pure JavaScript objects, without depending on the DOM or a native mobile environment:
import {createElement} from 'rax';
import renderer from 'rax-test-renderer';
const tree = renderer.create(
<Link page="https://example.com/">Example</Link>
);
console.log(tree.toJSON());
// { tagName: 'A',
// attributes: { href: 'https://example.com/' },
// children: [ 'Example' ] }
You can also use Jest's snapshot testing feature to automatically save a copy of the JSON tree to a file and check in your tests that it hasn't changed: http://facebook.github.io/jest/blog/2016/07/27/jest-14.html.
import {createElement} from 'rax';
import renderer from 'rax-test-renderer';
test('Link renders correctly', () => {
const tree = renderer.create(
<Link page="https://example.com">Example</Link>
).toJSON();
expect(tree).toMatchSnapshot();
});
You can inspect and modify the state of your Rax components at runtime using the React Developer Tools browser extension.
- Install the React Developer Tools extension
- Import the "rax/lib/devtools" module in your app
import 'rax/lib/devtools';
- Set
process.env.NODE_ENV
to 'development' - Reload and go to the 'React' tab in the browser's development tools
Add an alias for react
and react-dom
in webpack config that makes React-based modules work with Rax, without any code changes:
{
// ...
resolve: {
alias: {
'react': 'rax/lib/compat',
'react-dom': 'rax-dom'
}
}
// ...
}
Install TypeScript:
$ npm install typescript --save-dev
Install TypeScript loader for webpack:
$ npm install ts-loader --save-dev
Create or update webpack.config.js
:
module.exports = {
mode: "development",
devtool: "inline-source-map",
entry: "./app.tsx",
output: {
filename: "bundle.js"
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
// all files with a `.ts` or `.tsx` extension will be handled by `ts-loader`
{ test: /\.tsx?$/, loader: "ts-loader" }
]
}
};
Add a tsconfig.json
file:
{
"compilerOptions": {
"sourceMap": true,
"jsx": "react",
"jsxFactory": "createElement"
}
}
Write your first TypeScript file using Rax:
// app.tsx
import { createElement, render } from 'rax';
import * as DriverDOM from 'driver-dom';
interface HelloProps { compiler: string; framework: string; }
const Hello = (props: HelloProps) => <h1>Hello from {props.compiler} and {props.framework}!</h1>;
render(<Hello compiler="TypeScript" framework="React" />, document.body, { driver: DriverDOM});
- createElement(type, [props], [...children])
createElement('div', { id: 'foo' }, createElement('p', null, 'hello world'));
- Fragment
<Fragment> <header>A heading</header> <footer>A footer</footer> </Fragment>
- createRef()
const inputRef = createRef(); function MyComponent() { return <input type="text" ref={inputRef} />; }
- forwardRef()
const MyButton = forwardRef((props, ref) => ( <button ref={ref}> {props.children} </button> )); // You can get a ref directly to the DOM button: const ref = createRef(); <MyButton ref={ref}>Click me!</MyButton>;
- useState()
function Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
- useEffect()
function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
- useLayoutEffect()
function Example() { const [count, setCount] = useState(0); useLayoutEffect(() => { // Fires in the same phase as componentDidMount and componentDidUpdate }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
- useContext()
// Create a Context const NumberContext = createContext(); function Example() { const value = useContext(NumberContext); return <div>The answer is {value}.</div>; }
- useRef()
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
- useCallback()
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
- useMemo()
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- useReducer()
const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'reset': return initialState; case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: // A reducer must always return a valid state. // Alternatively you can throw an error if an invalid action is dispatched. return state; } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, {count: initialCount}); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset'})}> Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }
- useImperativeHandle()
function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} />; } FancyInput = forwardRef(FancyInput);
- memo()
function MyComponent(props) { /* render using props */ } function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */ } export default memo(MyComponent, areEqual);
- render(element [, container] [, options] [, callback])
render(<HelloMessage name="world" />, document.body, { driver: DomDriver })
- Component
- PureComponent
- version
- Children
- Children.map(children, function[(thisArg)])
- Children.forEach(children, function[(thisArg)])
- Children.count(children)
- Children.only(children)
- Children.toArray(children)
- PropTypes
- PropTypes.array
- PropTypes.bool
- PropTypes.func
- PropTypes.number
- PropTypes.object
- PropTypes.string
- PropTypes.symbol
- PropTypes.element
- PropTypes.node
- PropTypes.any
- PropTypes.arrayOf
- PropTypes.instanceOf
- PropTypes.objectOf
- PropTypes.oneOf
- PropTypes.oneOfType
- PropTypes.shape
- isValidElement(object)
- cloneElement(element, [props], [...children])
- createFactory(type)
- createPortal(child, container)
- hydrate(element, container[, callback])
- findDOMNode(component)
- unmountComponentAtNode(container)
- createClass()
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our guidelines for contributing.
![]() @yuanyan Core |
![]() @imsobear Development |
![]() @yacheng Universals & Components |
![]() @boiawang Loaders & Plugins |
![]() @wssgcg1213 DSL Runtimes & Loaders |