Hooks与Class在逻辑复用上的对比

Hooks与Class在逻辑复用上的对比

Content #

Hooks 带来的最大好处:逻辑复用 #

在之前的 React 使用中,有一点经常被大家诟病,就是非常难以实现逻辑的复用,必须借助于高阶组件等复杂的设计模式。但是高阶组件会产生冗余的组件节点,让调试变得困难。不过这些问题可以通过 Hooks 得到很好的解决。所以如果有人问你 Hooks 有什么好处,那么最关键的答案就是简化了逻辑复用。

就以刚才我们提到的绑定窗口大小的场景为例。如果有多个组件需要在用户调整浏览器窗口大小时,重新调整布局,那么我们需要把这样的逻辑提取成一个公共的模块供多个组件使用。以 React 思想,在 JSX 中我们会根据 Size 大小来渲染不同的组件,例如:

function render() {
  if (size === "small") return <SmallComponent />;
  else return <LargeComponent />;
}

这段代码看上去简单,但如果在过去的 Class 组件中,我们甚至需要一个比较复杂的设计模式来解决,这就是高阶组件。

在 Class 组件的场景下,我们首先需要定义一个高阶组件,负责监听窗口大小变化,并将变化后的值作为 props 传给下一个组件。

const withWindowSize = Component => {
  // 产生一个高阶组件 WrappedComponent,只包含监听窗口大小的逻辑
  class WrappedComponent extends React.PureComponent {
    constructor(props) {
      super(props);
      this.state = {
        size: this.getSize()
      };
    }
    componentDidMount() {
      window.addEventListener("resize", this.handleResize);
    }
    componentWillUnmount() {
      window.removeEventListener("resize", this.handleResize);
    }
    getSize() {
      return window.innerWidth > 1000 ? "large" "small";
    }
    handleResize = ()=> {
      const currentSize = this.getSize();
      this.setState({
        size: this.getSize()
      });
    }
    render() {
      // 将窗口大小传递给真正的业务逻辑组件
      return <Component size={this.state.size} />;
    }
  }
  return WrappedComponent;
};

这样,在你的自定义组件中可以调用 withWindowSize 这样的函数来产生一个新组件,并自带 size 属性,例如:

class MyComponent extends React.Component{
  render() {
    const { size } = this.props;
    if (size === "small") return <SmallComponent />;
    else return <LargeComponent />;
  }
}
// 使用 withWindowSize 产生高阶组件,用于产生 size 属性传递给真正的业务组件
export default withWindowSize(MyComponent);

在这里,我们可以看到,为了传递一个外部的状态,我们不得不定义一个没有 UI 的外层组件,而这个组件只是为了封装一段可重用的逻辑。更为糟糕的是,高阶组件几乎是 Class 组件中实现代码逻辑复用的唯一方式,其缺点其实比较显然:

  1. 代码难理解,不直观,很多人甚至宁愿重复代码,也不愿用高阶组件;
  2. 会增加很多额外的组件节点。每一个高阶组件都会多一层节点,这就会给调试带来很大的负担。

可以说,正因为这些缺点被抱怨已久,React 团队才终于提出了 Hooks 这样一个全新的方案。那么现在我们不妨看一下,同样的逻辑如果用 Hooks 和函数组件该如何实现。首先我们需要实现一个 Hooks:

const getSize = () => {
  return window.innerWidth > 1000 ? "large" : "small";
}
const useWindowSize = () => {
  const [size, setSize] = useState(getSize());
  useEffect(() => {
  const handler = () => {
      setSize(getSize())
    };
    window.addEventListener('resize', handler);
    return () => {
      window.removeEventListener('resize', handler);
    };
  }, []);

  return size;
};

这样,我们在组件中使用窗口大小就会非常简单:

const Demo = () => {
  const size = useWindowSize();
  if (size === "small") return <SmallComponent />;
  else return <LargeComponent />;
};

可以看到,窗口大小是一个外部的一个数据状态,我们通过 Hooks 的方式对其进行了封装,从而将其变成一个可绑定的数据源。这样当窗口大小发生变化时,使用这个 Hook 的组件就都会重新渲染。而且代码也更加简洁和直观,不会产生额外的组件节点。

Viewpoints #

From #

02|理解 Hooks:React 为什么要发明 Hooks?