一个最简单的Hooks
首先让我们看一下一个简单的有状态组件:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
你还在担心是用无状态组件(函数)还是有状态组件(类)吗?
-有了钩子,你就不需要再写类了,你所有的组件都是函数。
你还在为使用哪个生命周期钩子函数而彻夜难眠吗?
-有了钩子,可以先把生命周期钩子函数扔到一边。
你还在对组件中的这一点感到困惑吗?
-既然所有类都丢失了,这是哪里?有生以来第一次,你不再需要面对这些。
这样看来,说React Hooks
是今年最激动人心的新功能,真的一点也不为过。如果你也对react感兴趣,或者正在使用react进行项目开发,请向我保证你会抽出至少30分钟来阅读这篇文章,好吗?
我们来看看使用钩子后的版本:
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
是不是轻松多了!可以看到,Example
变成了一个函数,但是这个函数有自己的状态(count),它也可以更新自己的状态(setCount)。这个函数如此伟大的原因是它注入了一个hook - useState
。正是这个钩子使得我们的函数成为有状态函数。
除了钩子的使用状态,还有许多其他的钩子。比如useEffect
提供了componentDidMount
等类似于生命周期钩子的功能,usecontext
提供了context
的功能等等。
钩子本质上是一种特殊的函数,可以在你的函数组件中注入一些特殊的函数。呃?这听起来有点像被批评的Mixins
,不是吗?Mixins
会在react中复兴吗?当然不是。后面再讲两者的区别。总而言之,这些钩子的目标是让你不再写类,让函数主宰世界。
React为什么要搞一个Hooks?
复用有状态组件太麻烦了!
我们都知道react的核心思想是将一个页面拆分成一堆独立的、可复用的组件,并将这些组件以自上而下单向数据流的形式串联起来。但是如果你在一个大型的工作项目中使用react
,你会发现项目中的很多react组件实际上都很冗长,很难重用。尤其是那些用类写的组件,里面包含了状态本身,重用这样的组件非常麻烦。
在此之前,官方的推荐是如何解决这个问题的?答案是:渲染道具和高阶组件。我们可以通过下面运行一点来简单看一下这两种模式。
渲染是指使用一个其值为函数的道具来传递需要动态渲染的节点或组件。如下面的代码所示,我们的DataProvider
组件包含所有与状态相关的代码,而Cat组件可以是一个简单的显示组件,因此DataProvider
可以单独重用。
import Cat from 'components/cat'
class DataProvider extends React.Component {
constructor(props) {
super(props);
this.state = { target: 'Zac' };
}
render() {
return (
<div>
{this.props.render(this.state)}
</div>
)
}
}
<DataProvider render={data => (
<Cat target={data.target} />
)}/>
虽然这个模式叫Render Props,但不是说非用一个叫render的props不可,习惯上大家更常写成下面这种:
...
<DataProvider>
{data => (
<Cat target={data.target} />
)}
</DataProvider>
高阶分量的概念更容易理解。说白了,函数接受一个组件作为参数,经过一系列处理,最后返回一个新的组件。请看下面的代码示例。withUser函数是一个高级组件。它返回一个新的组件,该组件具有获取它提供的用户信息的功能。
const withUser = WrappedComponent => {
const user = sessionStorage.getItem("user");
return props => <WrappedComponent user={user} {...props} />;
};
const UserPage = props => (
<div class="user-container">
<p>My name is {props.user}!</p>
</div>
);
export default withUser(UserPage);
上面两个模式看起来都挺不错的,很多库也用这个模式,比如我们常用的React路由器。但是如果我们仔细观察这两种模式,会发现它们会增加我们代码的层次关系。最直观的体现,打开devtool看看你的组件级嵌套是否夸张。这时,让我们回头看看上一节给出的钩子例子。是不是简洁很多,没有多余的层次嵌套?把各种想要的函数写成可重用的自定义钩子。当你的组件想要使用任何函数的时候,直接在组件中调用这个钩子就可以了。