React从Class方式转Hooks详解
目录
- React Hooks
- 前言
- Why Hooks ?
- For Class
- For Function
- Class & Hooks 对比
- Hooks如何保存组件状态和使用生命周期?
- 1、useState:让函数具有维持状态的能力
- 2、useEffect:执行副作用
- 总结
React Hooks
前言
之前工作三年中一直在用class方式去写前端功能页面这些,其实接触hooks也是有一定时间了。在第一次接触的时候应该是看了一门关于electron+react的项目的课程的时候。当时主要是去看electron,所以对hooks没有特别的关注。也有可能是长期以来对class的熟悉,当时对hooks这种函数式的写法也有一些抵触。但是由于业内对hooks的好评,曾经也想去用hooks去起一个项目,但是由于当时项目周期以及已有的技术栈等原因,一直想实践也没机会去实践使用。
由于,最近接手新的项目一直使用的是react hooks+ts这一套。于是,就得开始使用hooks了。其实就功能开发而言,照猫画虎,其实工作也没有什么问题的。但是由于一直没有系统的对hooks进行深入一步的了解,导致在很多时候,其实并不是很清楚为什么要这样去使用,于是最近去找了《React Hooks核心原理与实战》去进行了学习。
在使用层面以及原因层面上,重新审视hooks的使用以及为什么要使用hooks。函数式写法究竟有哪些好处进行了更进一步的思考。其实,在一定程度而言,也还是浅尝辄止,这里仅仅将我这段时间学习记录下来。
Why Hooks ?
Hooks很大的一个亮点是可以进行业务逻辑的重用。这一点在hooks中体现的尤为明显。比如,往常的class中如果要去监听窗口大小的变化的时候,就得在组件中在挂载后去添加监听事件,但是如果当另外一个地方需要用到这种监听窗口大小功能的话,这种逻辑代码并不可以复用,只能在那个组件中重新写一遍。但是在hooks中,我们可以将这部分监听的逻辑代码进行hooks方式封装,完全可以做到逻辑上的复用。
For Class
- 使用Class作为React的载体的时候:
- 组件之间不会相互继承,没有利用到class的继承的特性UI是状态驱动的,所有的方法都是内部调用或者作为生命周期的方法内部自动调用。没有使用到类的实例方法可以调用的特性
For Function React中的一个核心就是要实现从State数据到View试图层面的一个绑定。使用函数,其实更好的去解决State到View的一个映射问题。但是,用函数作为React的载体就会出现两个问题,函数中状态的保存以及生命周期的方法。
- Hooks怎样去解决上面两个问题:把一个外部的数据绑定到函数的执行。当数据变化时,让函数能够自动重新执行。这样的话,任何会影响 UI 展现的外部数据,都可以通过这个机制绑定到 React 的函数组件。
- Hooks钩子的理解:把某个目标结果钩到某个可能会变化的数据源或者事件源上,那么当被钩到的数据或事件发生变化时,产生这个目标结果的代码会重新执行,产生更新后的结果
文章图片
图解:一个执行过程(Execution),例如是函数组件本身,可以绑定在(钩在)传统意义的 State,或者 URL,甚至可以是窗口的大小。这样当 State、URL、窗口大小发生变化时,都会重新执行某个函数,产生更新后的结果。
Class & Hooks 对比
- 比起 Class 组件,函数组件是更适合去表达 React 组件的执行的,因为它更符合 State => View 这样的一个逻辑关系。但是因为缺少状态、生命周期等机制,让它一直功能受限。Hooks解决了函数组件作为React载体的状态生命周期等受限问题,让其功能充分发挥出来
- Hooks 中被钩的对象,可以是某个独立的数据源,也可以是另一个 Hook 执行的结果,这就带来了 Hooks 的最大好处:逻辑的复用
- 简化了逻辑复用
- Class方式中:使用高阶组件的设计模式进行逻辑复用。比如:我们要去复用一个窗口resize的功能,我们需要去定义一个没有UI的外层组件,去写相关resize的逻辑定义,然后将数据结果用属性的方式传给子组件。组件要复用这个逻辑的话,必须外层用这个组件包裹并返回。针对整个而言,**为了传递一个外部的状态,我们不得不定义一个没有 UI 的外层组件,而这个组件只是为了封装一段可重用的逻辑。**频繁使用,每一个高阶组件的使用都会多一层节点,会给调试等带来很大的负担。
//Class中高阶组件实现resize方法复用//1、高阶组件的声明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; }}return WrappedComponent; }; //2、组件MyComponent使用高阶组件中的resize功能class MyComponent extends React.Component{render() {const { size } = this.props; if (size === "small") return ; else return ; }}// 使用 withWindowSize 产生高阶组件,用于产生 size 属性传递给真正的业务组件export default withWindowSize(MyComponent);
- Hooks方式中:实现resize的话,窗口大小只是外部的一个数据状态。我们使用hooks方式对其封装,只是将其变成了一个可以绑定的数据源,当窗口大小发生变化的时候,这个组件也会重新渲染代码会更加简洁直观,并且不会产生额外的组件节点。
//Hooks中使用hooks方法进行resize逻辑复用//定义useWindowSize这个hookconst 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; }; //函数组件中使用这个hookconst Demo = () => {const size = useWindowSize(); if (size === "small") return ; else return; };
- 有助于关注分离
文章图片
图解:左侧是 Class 组件,右侧是函数组件结合 Hooks。蓝色和黄色代表不同的业务功能
Hooks如何保存组件状态和使用生命周期?
React一共提供了10个
Hooks
,useState
、useEffect
、useCallback
、useMemo
、useRef
、useContext
等等1、useState:让函数具有维持状态的能力 我们要遵循的一个原则就是:state 中永远不要保存可以通过计算得到的值,例如:
- 从 props 传递过来的值。有时候 props 传递过来的值无法直接使用,而是要通过一定的计算后再在 UI 上展示,比如说排序。那么我们要做的就是每次用的时候,都重新排序一下,或者利用某些 cache 机制,而不是将结果直接放到 state 里。
- 从 URL 中读到的值。比如有时需要读取 URL 中的参数,把它作为组件的一部分状态。那么我们可以在每次需要用的时候从 URL 中读取,而不是读出来直接放到 state 里。
- 从 cookie、localStorage 中读取的值。通常来说,也是每次要用的时候直接去读取,而不是读出来后放到 state 里。
2、useEffect:执行副作用 副作用是指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求。形式:
useEffect(callback, dependencies)
。涵盖了componentDidMount
、componentDidUpdate
和componentWillUnmount
三个生命周期方法。简而言之,useEffect 是每次组件 render 完后判断依赖并执行。使用useEffect应该注意的点:
没有依赖项,则每次 render 后都会重新执行
useEffect(()=>{ console.log('re-render')//每次render完成一次后就执行})
- 空数组作为依赖项,则只在首次执行时触发,对应到 Class 组件就是 componentDidMount
useEffect(()=>{console.log('did mount')//相当于componentDidMount},[])
- 可以返回一个函数,用在组件销毁的时候做一些清理的操作
const [size,setResize] = useState({})useEffect(()=>{ const handler = () => {setResize() } window.addEventListener('resize',handler) return ()=>{window.removeEventListener('resize',handler) }},[])
总结
- useEffect使用的四个场景 每次 render 后执行:不提供第二个依赖项参数。比如useEffect(() => {})。
- 仅第一次 render 后执行:提供一个空数组作为依赖项。比如useEffect(() => {}, [])。
- 第一次以及依赖项发生变化后执行:提供依赖项数组。比如useEffect(() => {}, [deps])。
- 组件 unmount 后执行:返回一个回调函数。比如useEffect() => { return () => {} }, [])。
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- 一个人的碎碎念
- 我从来不做坏事
- 从蓦然回首到花开在眼前,都是为了更好的明天。
- 西湖游
- 改变自己,先从自我反思开始
- leetcode|leetcode 92. 反转链表 II
- 从我的第一张健身卡谈传统健身房
- 自媒体形势分析
- 操作系统|[译]从内部了解现代浏览器(1)