React Split Components (RiC)

A new way of Function Components without Hooks.


Introduction

Introducing React Split Components →

React Function Components in closure style.

Write React like Svelte, just natural and fluent code.

Features

  • Remove the dependence on Hooks, but not purely Functional Components
  • Only at the writing level, no need for ESLint support
  • Like High-Order Components, it’s a “design pattern”, not API, no lib needed

Simple Example

function demo({ render }) {
  let count = 0;

  const onClick = () => {
    count += 1;
    render();
  };

  return () => (
    <>
      <h1>{count}</h1>
      <button onClick={onClick}>Click me</button>
    </>
  );
}

const Demo = create(demo);

Full Example

function demo({ render, onMounted, onUpdated }) {
  let props;

  // solve useState
  let loading = true;
  let data;
  let count = 0;

  // solve useMemo
  const getPower = (x) => x * x;
  let power = getPower(count);

  // solve useRef
  const countRef = { current: null };

  // solve useCallback
  const onClick = () => {
    const { setTheme } = props;
    setTheme();

    count = count + 1;
    power = getPower(count);

    render();
  };

  const getData = () => {
    request().then((res) => {
      data = res.data;
      loading = false;
      render();
    });
  };

  const onReload = () => {
    loading = true;
    render();
    getData();
  };

  // solve useEffect | useLayoutEffect
  onMounted(() => {
    getData();
  });

  onUpdated((prevProps) => {
    console.log(prevProps, props);
  });

  return (next) => {
    props = next;
    const { theme } = next;

    return (
      <>
        <h1>{loading ? 'loading...' : JSON.stringify(data)}</h1>
        <button onClick={onReload}>Reload data</button>
        <h1>{theme}</h1>
        <h1 ref={countRef}>{count}</h1>
        <h1>{power}</h1>
        <button onClick={onClick}>Click me</button>
      </>
    );
  };
}

const Demo = create(demo);

Online Demo

Edit react-split-components-final

Helper Function

create implementation example:

const create = (fn) => (props, ref) => {
  const [, setState] = useState(false);

  const hasMount = useRef(false);
  const prevProps = useRef(props);
  const layoutUpdated = useRef();
  const updated = useRef();
  const layoutMounted = useRef();
  const mounted = useRef();

  useLayoutEffect(() => {
    if (!hasMount.current || !layoutUpdated.current) return;
    layoutUpdated.current(prevProps.current);
  });

  useEffect(() => {
    if (!hasMount.current || !updated.current) return;
    updated.current(prevProps.current);
    prevProps.current = props;
  });

  useLayoutEffect(() => {
    if (layoutMounted.current) return layoutMounted.current();
  }, []);

  useEffect(() => {
    hasMount.current = true;
    if (mounted.current) return mounted.current();
  }, []);

  const [ins] = useState(() => {
    const render = () => setState((s) => !s);
    const onMounted = (callback, isLayout) => {
      if (typeof callback !== 'function') return;
      (isLayout ? layoutMounted : mounted).current = callback;
    };
    const onUpdated = (callback, isLayout) => {
      if (typeof callback !== 'function') return;
      (isLayout ? layoutUpdated : updated).current = callback;
    };
    return fn({ render, onMounted, onUpdated });
  });

  return ins(props, ref);
};

// const Demo = create(demo);
// const Demo = memo(create(demo)); // if memo
// const Demo = forwardRef(create(demo)); // if forwardRef

License

MIT License (c) nanxiaobei

GitHub

View Github