React从Class方式转Hooks详解
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 <Component size={this.state.size} />; } } return WrappedComponent; }; //2、组件MyComponent使用高阶组件中的resize功能 class MyComponent extends React.Component{ render() { const { size } = this.props; if (size === "small") return <SmallComponent />; else return <LargeComponent />; } } // 使用 withWindowSize 产生高阶组件,用于产生 size 属性传递给真正的业务组件 export default withWindowSize(MyComponent);
- Hooks方式中:实现resize的话,窗口大小只是外部的一个数据状态。我们使用hooks方式对其封装,只是将其变成了一个可以绑定的数据源,当窗口大小发生变化的时候,这个组件也会重新渲染代码会更加简洁直观,并且不会产生额外的组件节点。
//Hooks中使用hooks方法进行resize逻辑复用 //定义useWindowSize这个hook 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; }; //函数组件中使用这个hook const Demo = () => { const size = useWindowSize(); if (size === "small") return <SmallComponent />; else return <LargeComponent />; };
- 有助于关注分离
Hooks能够让针对同一个业务逻辑的代码尽可能聚合在一块,在Class组件中不得不吧同一个业务逻辑代码分散在类组件的不同的生命周期方法中
图解:左侧是 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 () => {} }, [])。
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注猪先飞的更多内容!
相关文章
PHP Fatal error: Cannot use object of type stdClass as array in错误
下面一起来看看在php开发中碰到PHP Fatal error: Cannot use object of type stdClass as array in错误问题的解决办法吧。 普通的数组出现如下错误 代码...2016-11-25关于React Native报Cannot initialize a parameter of type'NSArray<id<RCTBridgeModule>>错误(解决方案)
这篇文章主要介绍了关于React Native报Cannot initialize a parameter of type'NSArray<id<RCTBridgeModule>>错误,本文给大家分享解决方案,需要的朋友可以参考下...2021-05-12React引入antd-mobile+postcss搭建移动端
本文给大家分享React引入antd-mobile+postcss搭建移动端的详细流程,文末给大家分享我的一些经验记录使用antd-mobile时发现我之前配置的postcss失效了,防止大家踩坑,特此把解决方案分享到脚本之家平台,需要的朋友参考下吧...2021-06-21- 这篇文章主要介绍了React使用高德地图的实现示例(react-amap),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-18
使用 React 和 Threejs 创建一个VR全景项目的过程详解
这篇文章主要介绍了使用 React 和 Threejs 创建一个VR全景项目的过程详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-06- 思路其实没有那么复杂,把地图想成一个盒子容器,地图中心点想成盒子中心点;扎点在【地图中心点】不会动,当移动地图时,去获取【地图中心点】经纬度,设置某个位置的时候,将经纬度设置为【地图中心点】即可...2021-06-20
- 这篇文章主要为大家详细介绍了React列表栏及购物车组件使用,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-06-28
- 这篇文章主要介绍了react使用antd表单赋值,用于修改弹框的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-29
- 这篇文章主要介绍了React Native 启动流程简析,文以 react-native-cli 创建的示例工程(安卓部分)为例,给大家分析 React Native 的启动流程,需要的朋友可以参考下...2021-08-18
- 这篇文章主要介绍了一百多行代码实现react拖拽hooks,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-21
- useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state,接下来通过一个示例来看看怎么使用 useState吧...2021-06-04
idea 无法创建Scala class 选项的原因分析及解决办法汇总
这篇文章主要介绍了idea 无法创建Scala class 选项的解决办法汇总,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-09-02- 高阶组件就是接受一个组件作为参数并返回一个新组件(功能增强的组件)的函数。这里需要注意高阶组件是一个函数,并不是组件,这一点一定要注意,本文给大家分享React 高阶组件HOC使用小结,一起看看吧...2021-06-13
- 这篇文章主要介绍了React中使用setInterval函数的实例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-06
浅析Swift中struct与class的区别(汇编角度底层分析)
这篇文章主要介绍了Swift中struct与class的区别 ,本文从汇编角度分析struct与class的区别,通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-30- 这篇文章主要介绍了前端JavaScript中的class,类是用于创建对象的模板,JavaScript中的Class更多的还是语法糖,本质上绕不开原型链,下面就来看看关于JavaScript class类的详细内容吧...2021-10-22
- 本文主要介绍了react为什么不推荐使用index作为key,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-07-22
关于antd tree和父子组件之间的传值问题(react 总结)
这篇文章主要介绍了关于antd tree 和父子组件之间的传值问题,是小编给大家总结的一些react知识点,本文通过一个项目需求实例代码详解给大家介绍的非常详细,需要的朋友可以参考下...2021-06-02- 这篇文章主要介绍了react自动化构建路由的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-23
- 这篇文章主要介绍了React Class组件生命周期,包括react组件的两种定义方式和class组件不同阶段生命周期函数执行顺序,本文给大家介绍的非常详细,需要的朋友可以参考下...2021-08-14