likes
comments
collection
share

一行代码实现 React Hooks 与 JSX 的混写:comp-in-one

作者站长头像
站长
· 阅读数 3

大家好,今天是 4 月 1 日愚人节,我想向大家介绍一个我开发的 NPM 包comp-in-one。它通过某种“黑魔法”,可以达到在 JSX 中写 React Hooks 的效果,无需额外创建组件。下面我们就一起来看看它是如何使用的吧。

基础用法

首先通过 npm 安装 comp-in-one

npm install comp-in-one

然后在代码中引入 Comp 组件:

import { Comp } from 'comp-in-one';

现在我们可以直接在 JSX 中使用它了:

<Comp>
  {() => <div>Hello World</div>}
</Comp>

没错,就是这么简单!Comp 组件接受一个返回 JSX 的函数作为 children。在函数内部,我们可以像平常一样写 JSX。

与 React Hooks 混写

你可能会问:上面的例子有什么特别的?为什么要无缘无故多加一层函数?实际上,comp-in-one 的一大亮点体现在可以让我们直接在 JSX 中使用 React Hooks。比如:

<Comp>
  {() => {
    const [count, setCount] = useState(0);
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>Click me</button>
      </div>
    );
  }}
</Comp>

Comp 的 children 函数中,我们可以像在组件里那样使用 useStateuseEffect 等各种 Hooks,非常方便。

嵌套使用

Comp 组件是可以嵌套使用的。比如:

<Comp>
  {() => {
    const [count, setCount] = useState(0);
    return (
      <div>
        <Comp>
          {() => {
            useEffect(() => {
              console.log('inner comp mounted');
            }, []);
            return <p>You clicked {count} times</p>;
          }}
        </Comp>
        <button onClick={() => setCount(count + 1)}>Click me</button>
      </div>
    );
  }}
</Comp>

在外层 Comp 中定义的状态和函数,在内层的 Comp 中也可以访问到,无需 prop drilling。

条件渲染

通过设置不同的 keycomp-in-one 支持条件渲染。比如:

<>
  {isTrue && (
    <Comp key="a">
      {() => {
        const [count, setCount] = useState(0);
        return <div>{count}</div>;
      }}
    </Comp>
  )}
  {!isTrue && (
    <Comp key="b">
      {() => {
        useEffect(() => {
          console.log('mounted');
        }, []);
        return <div>Content</div>;
      }}
    </Comp>
  )}
</>

在条件渲染的场景下,设置了不同key的 Comp 组件内部使用的 Hooks 数量可以不一致。这并不会触发 React 开发模式的警告。

优化重渲染

尽管 Comp 组件在 JSX 中的使用非常方便,但有的时候我们还是希望对其进行优化,避免不必要的重渲染。这时可以利用 useMemoComp 组件进行包装:

const MyComp = () => {
  const [count, setCount] = useState(0);

  return;
  <div>
    <p>{count}</p>
    <button onClick={() => setCount((c) => c + 1)}>+</button>
    <Comp>
      {() =>
        useMemo(
          () => (
            <Comp>
              {() => {
                return <div>foo</div>;
              }}
            </Comp>
          ),
          [],
        )
      }
    </Comp>
  </div>;
};

在上面的代码中,我们用 useMemo 包装了 Comp 组件,传入一个空数组作为依赖数组,并在外部再包装一层 Comp 来保证 Hooks 的正确使用。这样当父组件的 count 状态发生变化时,内部的 Comp 不会被重新执行,从而避免了重渲染。

这种优化方式在 Comp 组件内部渲染开销比较大时尤为有用。合理使用 useMemo ,可以让我们在享受 comp-in-one 带来的灵活性的同时,也能保证应用的性能。

原理揭秘

文章看到这里,相信大家已经学会了 comp-in-one 的使用,也体会到了它可能带来的便利。那么它的内部是如何实现的呢?

其实 comp-in-one 的源码非常简洁,整个包的核心仅仅是一行代码:

export function Comp({ children }) {
  return children();
}

没错,Comp 组件就是简简单单地执行传入的 children 函数并返回结果而已。之所以能够处理 Hooks,是因为 React 看到的是 Comp 这一个组件,期望它返回一个 ReactElement,React 并不关心内部的过程。而我们利用这个机会,在内部执行传入的 children 函数,让其中的 Hooks 调用成为在组件顶层的合法调用。事实上,在这个过程中,我们很好地遵循了 React Hooks 的规则。

总结

comp-in-one 通过一个接口简洁的 Comp 组件,利用 React 本身的机制,极大简化了组织 Hooks 与 JSX 代码的方式。当然,它绝不是“银弹”,在实际的大型项目中,我们应该合理地拆分组件,组织代码结构。但 comp-in-one 给了我们一个全新的思路,让我们看到 React 开发可以更灵活、更高效。

希望今天的分享对大家有所启发。如果你想深入了解 comp-in-one,欢迎访问 GitHub 仓库:github.com/CoolSpring8…

Happy coding!