共计 3365 个字符,预计需要花费 9 分钟才能阅读完成。
Hook 简介
Hook 是 React 16.8 的新增特性。是对 React 函数组件的一种扩展,通过提供一些特殊的函数,让无状态的组件拥有状态组件才拥有的能力。
没有 Hook 之前写组件有两种形式,分别为
- 函数组件
- class 组件
函数组件特点如下
- 所有的数据都是依赖 props 传入的,没有内部 state
- 没有生命周期
- 没有 this(组件实例)
实际开发中因为业务复杂,一般使用函数组件无法满足,所以大家默认都是使用 class 组件进行开发,这是一个不会出错的选择。函数组件大家平时应该都挺少会去用的,因为函数组件能提供的功能比较局限。但是引入 Hook 后,函数的能力就被扩展了许多,因为函数的特性,非常适合抽象成可复用的组件。
解决哪些问题
不同组件间与状态有关的逻辑复用问题
平时写组件的方式就是通过 props 传递给下一个组件,有些简单的情况这样也挺好的。但是当项目不断迭代,会发现当组件被多个模块多次引用,还是会多写一些重复的逻辑。因为受到到状态或者生命周期的影响,导致这部分逻辑却又很难拆出来。
引入 Hook 就可以将受状态或生命周期影响的组件抽出来。
业务发展导致组件日益庞大
最外层的代码集中维护许多 state 状态,导致页面引入越来越多毫无关联的模块,代码的可读性大大降低,有时候因为多个生命周期里面有大量不相关的逻辑,这样杂乱的代码容易引起 bug,也增加了其它开发人员维护的难度。
Hook 将组件中每一个相关的小模块拆分成一个函数,这样能够让组件即使庞大结构也是清晰的。
用法
- State Hook:在函数组件中使用 state
- Effect Hook:在函数组件中使用生命周期
- Custom Hook:自定义 Hook,可以将组件逻辑提取到函数中。(注意:自定义 Hook 是一个函数,其名称以“use”开头,函数内部可以调用其他的 Hook)
Hook 规则
Hook 本质就是 JavaScript 函数,但是在使用它时需要遵循两条规则
-
只在最顶层使用 Hook(不要在循环,条件或嵌套函数中调用)
-
只在 React 函数中调用 Hook
ESLint 插件
强制执行 hooks 的最佳实践
eslint-plugin-react-hooks
如何使用
详细的一些概念官方文档已经写得很全面了,可以参考:Hook 的官方文档。
接下来就用一个实际的例子来说明一下 State Hook 的使用
示例
产品第一版本需求如下:
现在有 小 A,小 B 两位同学,每位同学都处于未穿鞋的状态,小 A 穿鞋需要 2s,小 B 穿鞋需要 5s,在页面一中用文字描述两位同学的穿鞋状态变更 (如果小 A 正在穿鞋中,则在页面上显示 ‘ 小 A 正在穿鞋子 ’,如果小 A 已经穿好了鞋子,则将文字替换为 ‘ 小 A 已经穿好鞋子 ’)
使用 class 组件实现如下:
src/demo1.js
import React from "react";
class Page extends React.Component {
state = {
data: [{ id: 1, name: "小 A", time: "2000"},
{id: 2, name: "小 B", time: "5000"}
]
};
start(item) {
this.setState({[item.id]: "穿鞋子"
});
setTimeout(() => {
this.setState({[item.id]: "穿好鞋子"
});
}, item.time);
}
componentDidMount() {
this.state.data.forEach(item => {this.start(item);
});
}
render() {
return (
<div>
{this.state.data.map(item => {
return (<h1 key={item.id}>
{this.state[item.id] === "穿鞋子"
? `${item.name} 正在穿鞋子...`
: `${item.name} 已经穿好鞋子了 `}
</h1>
);
})}
</div>
);
}
}
export default Page;
使用 Hook 组件实现如下:
自定义 hook 如下:
src/useHook.js
import React, {useState} from "react";
function useHook(item) {const [status, setStatus] = useState("穿鞋子");
setTimeout(() => {setStatus("穿好鞋子");
}, item.time);
return (<h1 key={item.id}>
{status === "穿鞋子"
? `${item.name} 正在穿鞋子...`
: `${item.name} 已经穿好鞋子了 `}
</h1>
);
}
export default useHook;
引用 hook 的函数组件
src/hookDemo1.js
import React from "react";
import useHook from "./useHook";
function Page() {
const data = [{ id: 1, name: "小 A", time: "2000"},
{id: 2, name: "小 B", time: "5000"}
];
return (
<div>
{data.map(item => {return useHook(item);
})}
</div>
);
}
export default Page;
好了,实现完上面的需求,现在觉得 hook 的好处并没有什么体现,接下来产品又发布了第二版需求,要求我们在另一个页面显示另外两位同学的状态。需求如下:
现在有 小 C,小 D 两位同学,每位同学都处于未穿鞋的状态,小 A 穿鞋需要 4s,小 B 穿鞋需要 8s,在页面二中用文字描述两位同学的穿鞋状态变更 (如果小 A 正在穿鞋中,则在页面上显示 ‘ 小 C 正在穿鞋子 ’,如果小 C 已经穿好了鞋子,则将文字替换为 ‘ 小 C 已经穿好鞋子 ’)
接下来再第一个版本的基础上来实现第二个需求
使用 class 组件实现如下:
src/demo2.js
import React from "react";
class Page extends React.Component {
state = {
data: [{ id: 1, name: "小 C", time: "4000"},
{id: 2, name: "小 D", time: "8000"}
]
};
start(item) {
this.setState({[item.id]: "穿鞋子"
});
setTimeout(() => {
this.setState({[item.id]: "穿好鞋子"
});
}, item.time);
}
componentDidMount() {
this.state.data.forEach(item => {this.start(item);
});
}
render() {
return (<div style={{ color: "lightblue"}}>
{this.state.data.map(item => {
return (<h1 key={item.id}>
{this.state[item.id] === "穿鞋子"
? `${item.name} 正在穿鞋子...`
: `${item.name} 已经穿好鞋子了 `}
</h1>
);
})}
</div>
);
}
}
export default Page;
使用 Hook 组件实现如下:
src/hookDemo2.js
import React from "react";
import useHook from "./useHook";
function Page() {
const data = [{ id: 1, name: "小 C", time: "4000"},
{id: 2, name: "小 D", time: "8000"}
];
return (<div style={{ color: "lightblue"}}>
{data.map(item => {return useHook(item);
})}
</div>
);
}
export default Page;
第二次的代码明显比第一次少了许多,而且如果之后产品如果增加了一个状态,那明显使用 Hook 实现的方式可以更快的适应需求的变更,代码的维护性也变高了许多。
看完代码应该就能很好的理解 hook 的使用了吧,具体代码的运行点击在线演示查看
在线演示
总结
Hook 给我们带来的就是在函数的基础上可以加入状态和生命周期等函数不曾有的特性,这个特性的加入能够让我们更好的抽象组件,提高代码的复用性。