/fre

:ghost: Tiny React16 like library with Concurrent and Suspense.

Primary LanguageJavaScriptMIT LicenseMIT

fre logo

Fre

👻 Tiny React16 like library with Concurrent and Suspense.

Build Status Code Coverage npm-v npm-d gzip

Feature

  • 🎉 Functional Component and hooks API
  • 🎊 Concurrent and Suspense
  • 🔭 keyed reconcilation algorithm

Introduction

Fre (pronounced /fri:/, like free) is a tiny and perfect js library, It means Free! ~

Contributors

Fre has wonderful code, we need anyone to join us improve together.

Users or sponsors

Thanks for the following websites and sponsors, If you do the same, please tell us with issue~

Demo

Thanks for Rasmus Schultz

Use

yarn add fre
import { h, render, useState } from 'fre'

function Counter() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

render(<Counter />, document.getElementById('root'))

Hooks API

useState

useState is a base API, It will receive initial state and return a Array

You can use it many times, new state is available when component is rerender

function Counter() {
  const [up, setUp] = useState(0)
  const [down, setDown] = useState(0)
  return (
    <div>
      <h1>{up}</h1>
      <button onClick={() => setUp(up + 1)}>+</button>
      <h1>{down}</h1>
      <button onClick={() => setDown(down - 1)}>-</button>
    </div>
  )
}

render(<Counter />, document.getElementById('root'))

useReducer

useReducer and useState are almost the same,but useReducer needs a global reducer

function reducer(state, action) {
  switch (action.type) {
    case 'up':
      return { count: state.count + 1 }
    case 'down':
      return { count: state.count - 1 }
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 1 })
  return (
    <div>
      {state.count}
      <button onClick={() => dispatch({ type: 'up' })}>+</button>
      <button onClick={() => dispatch({ type: 'down' })}>+</button>
    </div>
  )
}

render(<Counter />, document.getElementById('root'))

useEffect

useEffect takes two parameters, the first is a effect callback and the second is an array

if the array changed, the callback will execute after commitWork, such as pureComponentDidUpdate

if the array is empty, it means execute once, such as componentDidMount

if no array, it means execute every time , such as componentDidUpdate

if useEffect returns a function, the function will execute before next commitWork, such as componentWillUnmount

function Counter({ flag }) {
  const [count, setCount] = useState(0)
  useEffect(() => {
    document.title = 'count is ' + count
  }, [flag])
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

render(<Counter />, document.getElementById('root'))

useCallback

useCallback has the same parameters as useEffect, but useCallback will return a cached function.

const set = new Set()

function Counter() {
  const [count, setCount] = useState(0)
  const cb = useCallback(() => {
    console.log('cb was cached')
  }, [count])
  set.add(cb)

  return (
    <div>
      <h1>{set.size}</h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

useMemo

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

function Counter() {
  const [count, setCount] = useState(0)
  const val = useMemo(() => {
    return new Date()
  }, [count])
  return (
    <div>
      <h1>
        {count} - {val}
      </h1>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

render(<Counter />, document.getElementById('root'))

useRef

useRef will return a function or an object.

function App() {
  useEffect(() => {
    console.log(t) // { current:<div>t</div> }
  })
  const t = useRef(null)
  return <div ref={t}>t</div>
}

If it use a function, It can return a cleanup and exectes when removed.

function App() {
  const t = useRef(dom => {
    if (dom) {
      doSomething()
    } else {
      cleanUp()
    }
  })
  return flag && <span ref={t}>I will removed</span>
}

props

Props are used for component communication

function App() {
  const [sex, setSex] = useState('boy')
  return (
    <div>
      <Sex sex={sex} />
      <button
        onClick={() => (sex === 'boy' ? setSex('girl') : setSex('boy'))}
      />
    </div>
  )
}
function Sex(props) {
  return <div>{props.sex}</div>
}

Props contains children to render all the child elements of itself

const HelloBox = () => (
  <Box>
    <h1>Hello world !</h1>
  </Box>
)

const Box = props => <div>{props.children}</div>

Hooks do not support HOC and extends, but render props are supported by default

const HelloBox = () => <Box render={value => <h1>{value}</h1>} />

const Box = props => <div>{props.render('hello world!')}</div>

Also can be render children

const HelloBox = () => (
  <Box>
    {value => {
      return <h1>{value}</h1>
    }}
  </Box>
)

const Box = props => <div>{props.children('hello world!')}</div>

JSX(JavaScript extension)

The default export h function needs to be configured

import { h } from 'fre'
{
  "plugins": [["transform-react-jsx", { "pragma": "h" }]]
}

If browser environment, recommend to use htm

Concurrent

Fre implements a tiny priority scheduler, which like react Fiber.

It can break the work, and when there are idle time, the work will continue.

Concurrent Mode is also called time slicing or concurrent mode.

Suspense

Suspense is another way to break the work.

It throws promise, and fre catches promise then suspend the work. It waits until resolve to the promise.

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 ©132yse inspired by react anu