React personal implementation version.
yarn
# need install parcel:
# - yarn global add parcel-bundler
# - npm install -g parcel-bundler
npm start
- Familiar React render component with virtual dom.
- Support
useState
hook.
- Support render
Class Component
. - Support Class Component
setState
andlifecycle api
. - Support other hooks api.
- support
useEffect
. - support
useReducer
. - support
useCallback
/useMemo
/useRef
. - support
useContext
.
- support
- Publish package.
- Add unit test.
- Clarify the code design and add necessary comments.
- render Function Component and Class Component.
- render useState Component.
- render useEffect Component.
- render useReducer Component.
- render useCallback Component.
- render useMemo Component.
- render useRef Component.
- render useContext Component.
import { React, useState } from "@caiym/react";
function Event() {
return <span>Event</span>;
}
function Log() {
return <span>Log</span>;
}
export default function HooksFunction() {
const [toggle, setToggle] = useState(false);
return (
<div>
<button
onClick={() => {
setToggle(!toggle);
}}
>
useState toggle
</button>
{toggle ? <Event /> : <Log />}
</div>
);
}
import { React } from "@caiym/react";
type ItemType = {
id: number;
text: string;
};
interface TodoAppProps {
title: string;
}
interface TodoAppState {
text: string;
items: ItemType[];
}
class TodoApp extends React.Component<TodoAppProps, TodoAppState> {
constructor(props: TodoAppProps) {
super(props);
this.state = {
items: [],
text: "",
};
}
handleChange = (e: MouseEvent) => {
const target = e.target as HTMLInputElement;
this.setState({ text: target.value });
}
handleSubmit = (e: MouseEvent) => {
const { text } = this.state;
e.preventDefault();
if (!text.length) {
return;
}
const newItem = {
text,
id: Date.now(),
};
this.setState((state: TodoAppState) => ({
items: state.items.concat(newItem),
text: '',
}));
}
render() {
const { title } = this.props;
const { items, text } = this.state;
return (
<div>
<h3>{title}</h3>
<TodoList items={items} />
<form>
<label htmlFor="new-todo">What needs to be done?</label>
<br />
<input
autocomplete="off"
id="new-todo"
onChange={this.handleChange}
value={text}
/>
<button onClick={this.handleSubmit}>Add #{items.length + 1}</button>
</form>
</div>
);
}
}
interface TodoListProps {
items: ItemType[];
}
class TodoList extends React.Component<TodoListProps> {
render() {
const { items } = this.props;
return (
<ol>
{items.map((item: ItemType) => (
<li key={item.id}>{item.text}</li>
))}
</ol>
);
}
}
export default TodoApp;
import { React, ReactDOM } from "@caiym/react";
import FunctionComponent from "./FunctionComponent";
import ClassComponent from "./ClassComponent";
class App extends React.Component {
render() {
return (
<div>
<FunctionComponent />
<ClassComponent title="React Todo" />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
import { useState } from "@caiym/react";
function UseStateComponent() {
const initialValue = 0;
const [ count, setCount ] = useState(initialValue);
return (
<div>
<h3>useState</h3>
<div>
<button onClick={() => setCount(count + 1)}>setCount</button>
<button onClick={() => setCount(initialValue)}>reset</button>
{count}
</div>
</div>
);
}
import { useState, useEffect } from "@caiym/react";
function UseEffectComponent() {
const [ toggle, setToggle ] = useState(false);
const [ count, setCount ] = useState(0);
useEffect(() => {
console.info('===run useEffect function===');
return () => {
console.info('===cleanup before re-run useEffect function===');
};
}, [toggle]);
return (
<div>
<h3>useEffect</h3>
<div style={{ fontSize: '12px', color: 'gray' }}>需要打开控制台查看运行结果</div>
<button onClick={() => setToggle(!toggle)}>
setToggle trigger run useEffect function, toggle: {String(toggle)}
</button>
<div>
<button onClick={() => setCount(count + 1)}>
setCount don't trigger run useEffect function, count: {count}
</button>
</div>
</div>
);
}
import { useReducer } from "@caiym/react";
const sleep = (ms: number = 500) => new Promise((resolve) => setTimeout(resolve, ms));
type ReducerState = {
count: number;
loading: boolean;
};
const ACTIONS = {
INCREASE: 'increase',
DECREASE: 'decrease',
LOADING: 'loading',
RESET: 'reset',
};
const initialState = {
count: 0,
loading: false,
};
const countReducer = (state: ReducerState, action: { type: string; [key: string]: string }) => {
switch (action.type) {
case ACTIONS.INCREASE:
return { ...state, loading: false, count: state.count + 1 };
case ACTIONS.DECREASE:
return { ...state, loading: false, count: state.count - 1 };
case ACTIONS.LOADING:
return { ...state, loading: true };
case ACTIONS.RESET:
return { ...state, loading: false, count: initialState.count };
default:
return state;
}
}
function UseReducerComponent() {
const [ state, dispatch ] = useReducer(countReducer, initialState);
const { count, loading } = state;
const onIncreaseHandler = async () => {
dispatch({ type: ACTIONS.LOADING });
await sleep();
dispatch({ type: ACTIONS.INCREASE });
};
const onDecreaseHandler = async () => {
dispatch({ type: ACTIONS.LOADING });
await sleep();
dispatch({ type: ACTIONS.DECREASE });
};
const onResetHandler = async () => {
dispatch({ type: ACTIONS.LOADING });
await sleep();
dispatch({ type: ACTIONS.RESET });
};
return (
<div>
<h3>useReducer</h3>
<div className="result">Count: {loading ? 'loading...' : count}</div>
<div className="operators">
<button onClick={onIncreaseHandler}>+</button>
<button onClick={onDecreaseHandler}>-</button>
<button onClick={onResetHandler}>reset</button>
</div>
</div>
);
}
import { useCallback, useState } from "@caiym/react";
function useInputValue(initialValue: string) {
const [ value, setValue ] = useState(initialValue);
// stable onChange prop, avoid unnecessary render
const onChange = useCallback((event: MouseEvent) => {
const target = event.target as HTMLInputElement;
setValue(target.value);
}, []);
return {
value,
onChange,
};
}
function UseCallbackComponent() {
const name = useInputValue('Jack');
return (
<div>
<h3>useCallback</h3>
<input type="text" {...name} />
<div>Value: {name.value}</div>
</div>
);
}
In fact, the example is't very suitable, because re-render of MemoChild component has been avoided by props comparison. For a better example, please look at the example of render useContext Component.
import { useMemo, useState } from "@caiym/react";
function MemoChild(props: {
count: number;
}) {
return useMemo(() => (
<div>MemoChild: {+new Date()}</div>
), [props.count]);
}
function UseMemoComponent() {
let [ count, setCount ] = useState(0);
let [ num, setNum ] = useState(0);
return (
<div>
<h3>useMemo</h3>
<div>Count: {count}</div>
<div>Number: {num}</div>
<MemoChild count={count} />
<button onClick={() => setCount(count + 1)}>
setCount to trigger MemoChild re-render
</button>
<br/>
<button onClick={() => setNum(num + 1)}>
setNum don't trigger MemoChild re-render
</button>
</div>
);
}
import { useRef } from "@caiym/react";
function UseRefComponent() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<div>
<h3>useRef</h3>
<input ref={inputEl} type="text"/>
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
}
import { React, useState, useContext } from "@caiym/react";
const CounterContext = React.createContext(0);
function ContextChild() {
const count = useContext(CounterContext);
return (
<div>count: {count}</div>
);
}
function ContextMemoChild(props: {
count: number;
}) {
return useMemo(() => (
<div>MemoChild: {+new Date()}</div>
), [props.count]);
}
function CommonChild(props: {
count: number;
}) {
return <div>CommonChild: {+new Date()}</div>;
}
function UseContextComponent() {
const [ count, setCount ] = useState(0);
return (
<div>
<h3>useContext</h3>
<CounterContext.Provider value={count}>
<ContextChild />
<div>
<ContextChild />
<ContextMemoChild count={0} />
<CommonChild count={0} />
</div>
</CounterContext.Provider>
<button onClick={() => setCount(count + 1)}>setCount</button>
</div>
);
}
Thank you here!
Welcome to commit issue & pull request !