Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/caiyongmin/tiny-react

React personal implementation version with unit test.
https://github.com/caiyongmin/tiny-react

diff hooks react typescript

Last synced: 2 months ago
JSON representation

React personal implementation version with unit test.

Awesome Lists containing this project

README

        

# creact

[![npm version](https://img.shields.io/npm/v/@caiym/react.svg?style=flat)](https://www.npmjs.com/package/@caiym/react) [![codecov](https://codecov.io/gh/caiyongmin/creact/branch/master/graph/badge.svg)](https://codecov.io/gh/caiyongmin/creact) ![](https://badgen.net/bundlephobia/minzip/@caiym/react) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](README.md)

[React](https://reactjs.org/) personal implementation version.

## Run

```bash
yarn

# need install parcel:
# - yarn global add parcel-bundler
# - npm install -g parcel-bundler
npm start
```

## Features

- Familiar React render component with virtual dom.
- Support `useState` hook.

## Todos

- [x] Support render `Class Component`.
- [x] Support Class Component `setState` and `lifecycle api`.
- [ ] Support other hooks api.
- [x] support `useEffect`.
- [x] support `useReducer`.
- [x] support `useCallback` / `useMemo` / `useRef`.
- [x] support `useContext`.
- [x] Publish package.
- [x] Add unit test.
- [ ] Clarify the code design and add necessary comments.

## Examples

- [render Function Component and Class Component](#render-function-component-and-class-component).
- [render useState Component](#render-usestate-component).
- [render useEffect Component](#render-useeffect-component).
- [render useReducer Component](#render-usereducer-component).
- [render useCallback Component](#render-usecallback-component).
- [render useMemo Component](#render-usememo-component).
- [render useRef Component](#render-useref-component).
- [render useContext Component](#render-usecontext-component).

### render Function Component and Class Component
↥ back to examples

[![Edit creact-simple-demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/epic-water-d6t2b?fontsize=14)

#### Function Component

```tsx
import { React, useState } from "@caiym/react";

function Event() {
return Event;
}

function Log() {
return Log;
}

export default function HooksFunction() {
const [toggle, setToggle] = useState(false);

return (


{
setToggle(!toggle);
}}
>
useState toggle

  
{toggle ? : }

);
}
```

#### Class Component

```tsx
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 {
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 (


{title}




What needs to be done?



  
Add #{items.length + 1}


);
}
}

interface TodoListProps {
items: ItemType[];
}

class TodoList extends React.Component {
render() {
const { items } = this.props;

return (


    {items.map((item: ItemType) => (
  1. {item.text}

  2. ))}

);
}
}

export default TodoApp;
```

#### Render

```tsx
import { React, ReactDOM } from "@caiym/react";
import FunctionComponent from "./FunctionComponent";
import ClassComponent from "./ClassComponent";

class App extends React.Component {
render() {
return (





);
}
}

ReactDOM.render(, document.getElementById("root"));
```

### render useState Component
↥ back to examples

[![Edit creact useState demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-usestate-demo-njttc?fontsize=14)

```tsx
import { useState } from "@caiym/react";

function UseStateComponent() {
const initialValue = 0;
const [ count, setCount ] = useState(initialValue);

return (


useState



setCount(count + 1)}>setCount
setCount(initialValue)}>reset
  
{count}


);
}
```

### render useEffect Component
↥ back to examples

[![Edit creact useEffect demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-useeffect-demo-nqt27?fontsize=14)

```tsx
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 (


useEffect


需要打开控制台查看运行结果

setToggle(!toggle)}>
setToggle trigger run useEffect function, toggle: {String(toggle)}


setCount(count + 1)}>
setCount don't trigger run useEffect function, count: {count}



);
}
```

### render useReducer Component
↥ back to examples

[![Edit creact useReducer demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-usereducer-demo-fvz45?fontsize=14)

```tsx
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 (


useReducer


Count: {loading ? 'loading...' : count}


+
-
reset


);
}
```

### render useCallback Component
↥ back to examples

[![Edit creact useCallback demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-usecallback-demo-422lf?fontsize=14)

```tsx
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 (


useCallback



Value: {name.value}


);
}
```

### render useMemo Component
↥ back to examples

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](#render-usecontext-component).

[![Edit creact useMemo demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-usememo-demo-r8rwp?fontsize=14)

```tsx
import { useMemo, useState } from "@caiym/react";

function MemoChild(props: {
count: number;
}) {
return useMemo(() => (

MemoChild: {+new Date()}

), [props.count]);
}
function UseMemoComponent() {
let [ count, setCount ] = useState(0);
let [ num, setNum ] = useState(0);

return (


useMemo


Count: {count}

Number: {num}


setCount(count + 1)}>
setCount to trigger MemoChild re-render



setNum(num + 1)}>
setNum don't trigger MemoChild re-render


);
}
```

### render useRef Component
↥ back to examples

[![Edit creact useRef demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-useref-demo-955mp?fontsize=14)

```tsx
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 (


useRef



Focus the input

);
}
```

### render useContext Component
↥ back to examples

[![Edit creact useContext demo](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/creact-usecontext-demo-f7bkf?fontsize=14)

```tsx
import { React, useState, useContext } from "@caiym/react";

const CounterContext = React.createContext(0);
function ContextChild() {
const count = useContext(CounterContext);
return (

count: {count}

);
}
function ContextMemoChild(props: {
count: number;
}) {
return useMemo(() => (
MemoChild: {+new Date()}

), [props.count]);
}
function CommonChild(props: {
count: number;
}) {
return
CommonChild: {+new Date()}
;
}

function UseContextComponent() {
const [ count, setCount ] = useState(0);
return (


useContext










setCount(count + 1)}>setCount

);
}
```

## Refs

Thank you here!

- [react-in-160-lines-of-javascript](https://medium.com/@sweetpalma/gooact-react-in-160-lines-of-javascript-44e0742ad60f).
- [从零开始实现一个React](https://github.com/hujiulong/blog/issues/4).
- [Preact](https://github.com/developit/preact).

Welcome to commit [issue](https://github.com/caiyongmin/creact/issues) & [pull request](https://github.com/caiyongmin/creact/pulls) !