1、什么是Hooks?
React Hooks是16.8之后新增一个React特性,它可以让你在函数组件中使用state等其它class组件的特性。
2、 为什么要有Hooks?
Class组件:
- 学习成本高
- 业务逻辑分散
- 逻辑复用困难
Hooks:
- 学习成本低
- 擅长逻辑复用
- 会产生很多闭包问题
为什么说Class学习成本高,而hooks学习成本低呢?
第一、生命周期,Class组件绕不开的一点就是生命周期的问题。React15、React16.3、React16.4到React17生命周期有了很多变化。生命周期在class组件中非常重要,面试基本都会问到。如:
-
react生命周期的变化、
-
如何优化Class组件、
-
一般在哪个生命周期函数中发送请求?
-
在这个生命周期函数之前可以发送请求吗?
第二、this指向的问题,在class组件中因为你不得不使用大量的bind或者箭头函数来定义函数。面试也会问到你:
-
为什么要这么写?
-
这么写的好处是什么?
-
不这样写会产生什么问题?
对比Class,Hooks的学习成本可就太低了。基本上只要能掌握useState和useEffect,那么80%的业务问题都能解决了。
为什么说Class业务逻辑分散,而hooks擅长逻辑复用呢?
我们在学习代码的第一天,就应该知道高内聚、低耦合这六字箴言。设计组件也是一样的,我们应当考虑代码的高可复用性。然而在class组件中,我们实现一个功能,就不得不把相同业务的一些逻辑分散到各个生命周期中。
比如:
如果在组件加载的时候引入一个定时器,那么在组件卸载的时候就一定要清除这个定时器。
componentDidMount(){
this.timer=setTimeout(this.fun,1000);
}
componenWillUnmount(){
if(this.timer){
clearTimeout(this.timer);
}
}
如果我们有一个请求用户信息的组件,当用户id发生变化的时候,我们就要重新发起请求,那我们就不得不使用两个生命周期函数来处理这个发送请求的逻辑。
componentDidMount(){
const {useId}=this.props;
this.loadUserInfo(useId);
}
componentWillReceiveProps(nextProps){
const {userId:nextUserId}=nextProps;
const {userId}=this.props;
if(userId!==nextUserId){
this.loadUserInfo(nextUserId)
}
}
注意:在React16版本这个componentWillReceiveProps被标记为unsafe之后,可以使用componentDidUpdate来代替。那么面试官可能就问你:为啥要弃用呢?
而Hooks的业务逻辑就非常聚合了。上面的例子:
useEffect(()=>{
const timer=setTimeout(fun,1000);
return ()=>{
clearTimeout(timer);
}
},[])
useEffect(()=>{
loadUserInfo(userId);
},[userId])
为什么说Class复用困难,而Hooks逻辑复用简单?
Class组件逻辑复用一般使用的都是HOC和Render Props。但这两者虽然都可以实现逻辑复用,但是并没有让组件和代码变得好用和优美起来,这二者都会存在的一个问题是,逻辑的复用会导致嵌套层级过深,形成嵌套地狱。使用class组件,表单组件、国际化、Redux等混在一块形成一长串的高阶组件的形式大家应该都见过。
React Hooks很好的解决了这个问题。组件的复用,完全可以使用自定义Hooks来实现。逻辑共享也成为了社区的主流。什么是逻辑共享?众所周知,组件=UI+逻辑复用,但是当一个组件的ui完全不满足我们的需要,哪怕逻辑完全一样,这个组件的复用性也是为0。但Hooks可以帮助我们讲UI与逻辑分开,实现相同逻辑在不同组件之间的共享。
3、Hooks解决了什么问题?
React Hooks的出现解决了很多问题:
- 函数组件有了state
- 解决了函数式组件没有生命周期的问题
- 解决状态逻辑难以共享的问题
React Hooks 主要解决的问题是状态逻辑共享的问题,是继render-props和 HOC之后的第三种状态逻辑共享方案,它不会产生 JSX 嵌套地狱问题。状态逻辑共享其实就是状态逻辑的复用,因为它只共享和复用数据处理的逻辑,不会共享数据本身。
1) render-props:
功能: 将一个组件内的 state 作为 props 传递给调用者, 调用者可以动态的决定如何渲染.
创建 render props 的方式
- 接收一个外部传递进来的 props 属性
- 将内部的 state 作为参数传递给调用组件的 props 属性方法.
class Mouse extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
render() {
return (
<div style={{ height: '100%' }}>
// 使用 render props 属性来确定要渲染的内容
{this.props.render(this.state)}
</div>
)}
}
// 调用方式:
<Mouse render={mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}/>
缺点
- 无法在 return 语句外访问数据
它不允许在 return 语句之外使用它的数据. 比如上面的例子, 不能在 useEffect 钩子或组件中的任何其他地方使用 x 和 y 值, 只能在 return 语句中访问.
- 嵌套(有解决方案,不在这里赘述)
它很容易导致嵌套地狱. 如下代码:
const MyComponent = () => {
return (
<Mouse>
{({ x, y }) => (
<Page>
{({ x: pageX, y: pageY }) => (
<Connection>
{({ api }) => {
// yikes
}}
</Connection>
)}
</Page>
)}
</Mouse>
)
};
2) HOC
学习 HOC 我们只需要记住以下 2 点定义,这里只做简单介绍:
- 创建一个函数, 该函数接收一个组件作为输入 除了组件, 还可以传递其他的参数
- 基于该组件返回了一个不同的组件.
function withSubscription(WrappedComponent, selectData) {
returnclass extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// 一些通用的逻辑处理
render() {
// ... 并使用新数据渲染被包装的组件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
优点:
不会影响内层组件的一个状态,降低了耦合度
缺点:
- 如果,子组件的props跟父组件props相同的时候,子组件自身的 props 值可能会被父组件的props覆盖.
- 无法清晰的标识数据的来源
- 可能会产生嵌套地狱
3) Hooks
Hooks也是用来解决状态逻辑共享的问题的。React Hooks 就像一个内置的打平 renderProps 库,我们可以随时创建一个值,与修改这个值的方法。看上去像 function 形式的 setState,其实这等价于依赖注入,与使用 setState 相比,这个组件是没有状态的。
function App() {
const [open, setOpen] = useState(false);
return (
<>
<Button type="primary" onClick={() => setOpen(true)}>
Open Modal
</Button>
<Modal
visible={open}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
/>
</>
);
}
优点:
- hook 可以重命名
- hook 会清晰地标注数据的来源
- hook 可以让你在 return 之外使用数据
- hook 不会嵌套
- hook的更新数据和状态的粒度要更细更小
- 简单易懂, 对比 hoc 和 render props 两种方式, 它非常直观, 也更容易理解.
缺点:
- 会产生闭包问题
- 对人员素质有了更高的要求,React Hooks模糊了(或者说是抛弃了)生命周期的概念,看似降低了学习门槛,其实是对开发人员的素质有了一个更高的提升,它带来了更高的学习心智(如Hooks生命周期的理解、Hooks Rules的理解、useEffect依赖项的判断等),相比Vue3.0即将推出的Hooks有较高的使用门槛。
- Hooks能解决组件功能复用,但没有很好地解决JSX的复用问题。
- 类拥有比函数更丰富的表达能力(OOP),React采用Hooks+Function Component(函数式)的方式其实是一种无奈的选择,试想一个挂载了十几个方法或属性的Class Component,用Function Component来写如何组织代码使得逻辑清晰?这背后其实是函数式编程与面向对象编程两种编程范式的权衡