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 组件中实现代码逻辑复用的唯一方式,其缺点其实比较显然:
- 代码难理解,不直观,很多人甚至宁愿重复代码,也不愿用高阶组件;
- 会增加很多额外的组件节点。每一个高阶组件都会多一层节点,这就会给调试带来很大的负担。
可以说,正因为这些缺点被抱怨已久,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?