Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fre2: New reconcilation algorithm #200

Merged
merged 33 commits into from
Dec 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 8 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p align="center"><img src="http://wx2.sinaimg.cn/mw690/0060lm7Tly1ftpm5b3ihfj3096097aaj.jpg" alt="fre logo" width="150"></p>
<h1 align="center">Fre</h1>
<p align="center">:ghost: Tiny React like framework with Concurrent.</p>
<p align="center">:ghost: Tiny Coroutine framework with Fiber.</p>
<p align="center">
<a href="https://github.com/yisar/fre/actions"><img src="https://img.shields.io/github/workflow/status/yisar/fre/main.svg" alt="Build Status"></a>
<a href="https://codecov.io/gh/yisar/fre"><img src="https://img.shields.io/codecov/c/github/yisar/fre.svg" alt="Code Coverage"></a>
Expand All @@ -9,17 +9,12 @@
<a href="https://bundlephobia.com/result?p=fre"><img src="http://img.badgesize.io/https://unpkg.com/fre/dist/fre.js?compression=brotli&label=brotli" alt="brotli"></a>
</p>

### Feature

- :tada: Functional Component and hooks API
- :confetti_ball: Time slicing and Algebraic effects
- :telescope: keyed reconcilation algorithm
- **Coroutine with Fiber** — This is an amazing idea, which implements the coroutine scheduler in JavaScript, and the rendering is asynchronous, which supports Time slicing and suspense components.

### Real world
- **Highly-optimized algorithm** — Fre has a better reconciliation algorithm, which traverses from both ends with O (n) complexity, and supports keyed.

[clicli.me](https://www.clicli.me)

Any other demos [click here](https://github.com/yisar/fre/tree/master/demo/src)
- **Do more with less** — After tree shaking, project of hello world is only 1KB, but it has most fetures, virtual DOM, hooks API, functional component and more.

### Use

Expand Down Expand Up @@ -59,8 +54,6 @@ render(<App />, document.getElementById('root'))

- [useRef](https://github.com/yisar/fre#useref)

- [useContext](https://github.com/yisar/fre#usecontext)

#### useState

`useState` is a base API, It will receive initial state and return a Array
Expand Down Expand Up @@ -102,7 +95,7 @@ function App() {
<div>
{state.count}
<button onClick={() => dispatch({ type: 'up' })}>+</button>
<button onClick={() => dispatch({ type: 'down' })}>+</button>
<button onClick={() => dispatch({ type: 'down' })}>-</button>
</div>
)
}
Expand Down Expand Up @@ -138,15 +131,15 @@ If it return a function, the function can do cleanups:
```js
useEffect(() => {
document.title = 'count is ' + count
reutn () => {
return () => {
store.unsubscribe()
}
}, [])
```

#### useLayout

More like useEffect, but useEffect queue in `requestAnimationFrame`, but useLayout is sync and block commitWork.
More like useEffect, but useLayout is sync and blocking UI.

```js
useLayout(() => {
Expand All @@ -156,7 +149,7 @@ useLayout(() => {

#### useMemo

`useMemo` has the same parameters as `useEffect`, but `useMemo` will return a cached value.
`useMemo` has the same rules as `useEffect`, but `useMemo` will return a cached value.

```js
function App() {
Expand Down Expand Up @@ -234,19 +227,5 @@ The above code needs babel plugin `@babel/plugin-transform-react-jsx`
]
```

#### time slicing

Time slicing is the scheduling of reconcilation, synchronous tasks, sacrifice CPU and reduce blocking time

#### resumable exception

resumable exception is a concept of algebraic effects. It can synchronously throw effects and then resume the execution of other logic of components.

#### key-based reconcilation

Fre implements a compact reconcilation algorithm support keyed, which also called diff.

It uses hash to mark locations to reduce much size.

#### License
_MIT @yisar
2 changes: 1 addition & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<body>
<div id="root"></div>
<script src="./src/use-state.tsx"></script>
<script src="./src/ref.tsx"></script>
</body>

</html>
56 changes: 56 additions & 0 deletions demo/src/keys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { h, render, useEffect, useState } from '../../src/index'

// function App() {
// const [key, setKey] = useState(['a', 'b', 'c'])
// return [
// <button onClick={() => setKey(['a', 'c', 'b','d'])}>x</button>,
// <ul>
// {key.map((i) => (
// <li key={i}>{i}</li>
// ))}
// </ul>,
// ]
// }

// function App() {
// const [key, setKey] = useState(['a', 'b', 'c'])
// return [
// <button onClick={() => setKey(['b', 'c', 'a'])}>x</button>,
// <ul>
// {key.map((i) => (
// <li key={i}>{i}</li>
// ))}
// </ul>,
// ]
// }

// function App() {
// const [key, setKey] = useState(['a', 'b', 'c'])
// return [
// <button onClick={() => setKey(['c', 'b','a'])}>x</button>,
// <ul>
// {key.map((i) => (
// <li key={i}>{i}</li>
// ))}
// </ul>,
// ]
// }

function App() {
const [key, setKey] = useState([1, 2])
return [
<button onClick={() => setKey([3, 2, 1])}>x</button>,
<ul>
{key.map((i) => (
<Li i={i} key={i} />
// <li key={i}>{i}</li>
))}
</ul>,
]
}

function Li(props) {
return <li>{props.i}</li>
}

render(<App />, document.getElementById('root'))
43 changes: 43 additions & 0 deletions demo/src/ref.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/** @jsx h */

// // preact:
// import { render, createElement as h } from "preact/compat";
// import { useState, useEffect } from "preact/hooks";

// react:
// import { createElement as h, useState, useEffect } from "react";
// import { render } from "react-dom";

// // fre:
import { render, h, useState, useEffect, useRef } from '../../src'


const Wrapper = () => {
const [showApp, setShowApp] = useState(true)

useEffect(()=>{
setTimeout(() => {
setShowApp(false)
}, 2000)
},[])

const p = dom => {
if (dom) {
} else {
console.log(111)
}
}
const c = dom => {
if (dom) {
} else {
console.log(222)
}
}
console.log(showApp)

return showApp ? <div ref={p}>
<p ref={c}>before</p>
</div> : <p>App removed...</p>
}

render(<Wrapper />, document.getElementById('root'))
13 changes: 6 additions & 7 deletions demo/src/use-effect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { h, render, useState, useEffect } from '../../src'
function Counter({ id, remove }) {
const [count, setCount] = useState(0)

useEffect(() => {
console.log(`111`)

return () => {
console.log(`222`)
}
})
// useEffect(() => {
// console.log(`111`)
// return () => {
// console.log(`222`)
// }
// })

return (
<div>
Expand Down
24 changes: 24 additions & 0 deletions demo/src/use-layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { h, render, useState, useEffect, useLayoutEffect } from '../../src'

function App() {
const [count, setCount] = useState(0)
return (
<div>
{count < 5 && <A count={count < 1 ? count : 2} />}
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
)
}

function A(props) {
useLayoutEffect(() => {
console.log(333)
return () => {
console.log(444)
}
})
return <div>{props.count}</div>
}

render(<App />, document.body)
2 changes: 1 addition & 1 deletion demo/src/use-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function App() {
// const [two, setTwo] = useState(0)
return (
<div>
<button onClick={() => setCount(count + 1)}>{count}</button>
<button onClick={() => setCount(count + 1)}>{count}{count}</button>
</div>
)
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
],
"scripts": {
"test": "jest --coverage",
"ref": "jest ./test/ref.test.tsx",
"build": "rollup -c && gzip-size dist/fre.js",
"build:compat": "rollup --config compat/rollup.config.js",
"dev": "rollup -c --watch",
Expand Down
4 changes: 1 addition & 3 deletions src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ export const updateElement = <P extends Attributes>(
if (oldValue) dom.removeEventListener(name, oldValue)
dom.addEventListener(name, newValue)
} else if (name in dom && !(dom instanceof SVGElement)) {
// for property, such as className
;(dom as any)[name] = newValue || ''
} else if (newValue == null || newValue === false) {
dom.removeAttribute(name)
} else {
// for attributes
dom.setAttribute(name, newValue)
}
}
Expand All @@ -36,7 +34,7 @@ export const createElement = <P = Attributes>(fiber: IFiber) => {
const dom =
fiber.type === 'text'
? document.createTextNode('')
: fiber.op & (1 << 4)
: fiber.tag & (1 << 4)
? document.createElementNS(
'http://www.w3.org/2000/svg',
fiber.type as string
Expand Down
7 changes: 3 additions & 4 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ export const useReducer = <S, A>(reducer?: Reducer<S, A>, initState?: S): [S, Di
hook[0] = isFn(hook[1]) ? hook[1](hook[0]) : hook.length ? hook[1] : initState
return [
hook[0] as S,
(action: A | Dispatch<A>) => {
hook[1] = reducer ? reducer(hook[0], action as A) : action
hook[2] = reducer && (action as any).type[0] === '*' ? 0b1100 : 0b1000
(value: A | Dispatch<A>) => {
hook[1] = reducer || value
dispatchUpdate(current)
},
]
Expand Down Expand Up @@ -67,5 +66,5 @@ export const getHook = <S = Function | undefined, Dependency = any>(cursor: numb
}

export const isChanged = (a: DependencyList, b: DependencyList) => {
return !a || a.length !== b.length || b.some((arg, index) => arg !== a[index])
return !a || b.some((arg, index) => arg !== a[index])
}
Loading