react中hook介绍以及使用教程
前言
最近由于公司的项目开发,就学习了在react关于hook的使用,对其有个基本的认识以及如何在项目中去应用hook。在这篇博客中主要从以下的几个点进行介绍:
- hook简介
- hook中常用api的使用
- hook在使用过程中需要去注意的地方
- hook中怎样去实现class组件中的声明周期函数
hook
首先介绍关于hook的含义,以及其所要去面对的一些场景
含义:Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。简单来说就是可以使用函数组件去使用react中的一些特性
所要解决的问题:
- 解决组件之间复用状态逻辑很难得问题,hook能解决的就是在你无需修改之前组件结构的情况下复用状态逻辑,在不使用hook的情况下,需要使用到一些高级的用法如高级组件、provider、customer等,这种方式对于新手来说不太友好,可能在理解上就比较的困难
- 对于复杂组件可以去拆分其逻辑,例如在你使用生命周期函数时,不同的生命周期需要在不同的时刻进行,因此在此时对于复杂的组件来说,有的生命周期函数中就存在大量的逻辑,在可读性上面就大打折扣。当使用hook时,就可以进行组件逻辑的划分,将相同的逻辑给整合在一起,这样就大大增加可读性也在一方面利于维护
- 不需要对于class组件的理解,当你在最初去学习时,你不得不去理解this这个关键字,在当前组件所表示的含义,但是在hook中就不需要。能够解决你在不使用class组件的情况下去体现react的特性
- 需要注意的一点就是hook和class组件是不能够同时使用的,在实际的使用过程中一定要注意,否则就会出现报错
那么接下来所要介绍的部分就是如何去使用hook
state hook
对于使用过class组件的同学,相信对于state肯定有很深的印象,对于一些需要用到的全局变量,在class组件中我们常常采用的方式是this.state = {},但是在hook中我们采用的方式就是使用useState这个hook,然后就可以对这种全局变量进行引用,在引用时只需要用其变量名即可,这里就拿官网的例子来举例:
import React, { useState } from 'react';
import React, { useState } from 'react'; function Example() { // 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
在上面的这个例子中,我们设置变量方式采用的就是const [count, setCount] = useState(0)这种方式,其中的0就是给count赋初值为0,如果想要给count赋值为一个空对象,那么只需要const [count, setCount] = useState({}),这样的方式就行了,那么这样你在用count时,此时获取到的值就为一个空对象。
作用:返回一个state,以及更新state的函数
- 函数式更新:新的state需要通过使用先前的state计算得出,将函数传递给setState,该函数将接收先前的state,并返回一个更新后的值
- 惰性初始state,initialState参数只会在组件的初始渲染中起作用,如果初始化state需要通过一个复杂计算来获取,则可以传入一个函数,在函数中计算并返回初始的state,此函数只在初始渲染时被掉用,如下所示:
const [state, setState] = useState(() => { const initialState = someExpensiveComputation(props); return initialState; })
在hook中如何给全局变量设置值
在class组件中我们给放在state中的变量赋值时,通常采用的方式就是this.setState()这种方式,那么在hook中所要采用的就是set+变量名这种方式,如
const [count, setCount] = useState(0)
在这里通过上面我们已经知道的就是count能够获取到值,那么其所对应的setCount(值),这种赋值的方式就是给count变量赋值的,然后通过count就能够获取到值。
- 为什么要采用这种方式呢?
- 原因:是因为react中的单向数据源,这样的话,能够保证你的数据源流向会更加的清楚,这也是react所区别于vue中双向数据源绑定的一点
hook中设置多个全局变量的方式
在hook中,如果我们需要去设置多个类似于上面所说的count,那么就需要多次使用useState这个hook,当然你也可以设置一个变量在hook的最外部,即在hook这个函数组件的外部。需要注意的是别在整个hook这个函数的全局设置,因此在hook的运行机制中,在每次加载时,都会从新去加载里面的变量,因此你是不能够去获取到在整个函数内部中使用该变量所改变的值的,能够获取到的就只是这个变量的初始值*
useEffect hook
对于useEffect hook,其用途类似于class组件中的生命周期函数,用来处理在一些特定时刻需要去做的事情,这种事情常被叫做副作用。在使用useEffect这个hook时,需要注意的一点就是其不能够被包含在循环,判断语句中,否则项目会出现报错,这也是hook的一种设置机制
- 副作用的划分:
- 不需要清除的: 在React更新DOM之后运行一些额外的代码:如:发送网络请求,手动变更DOM,记录日志等
- 需要清除的:当使用外部数据源时,需要去清除数据,如:定时器,需要我们在结束的时候去清除
- 渲染时机:在使用useEffect这个hook时,需要注意的就是其渲染的时机,默认情况下会在第一次渲染和每一次更新时去执行。对于如何去控制这个渲染时机,在下面的一个部分会有详细的介绍
- 作用:告诉组件在渲染之后执行某些操作
- useEffect放在组件内部调用的原因:可以在effect中直接访问state中的变量
- effect返回函数:effect可选的清除机制,每个effect都可以返回一个清除函数
- 接收内容:一个包含命令式、并且可能有副作用代码的函数
- 清除effect:实现方式,effect函数需要返回一个清除函数
- effect执行时机:在浏览器完成布局和绘制之后,传给useEffect的函数会延迟调用,因此不应该在函数中执行足赛浏览器更新屏幕的操作。
- 默认条件执行:会在每轮组件渲染完成后执行,因而一旦effect的依赖发生变化,他就会被重新创建。要改变其执行时机,需要给useEffect传递第二个参数,只有当第二个参数值发生改变才会重新创建订阅。如果要使用这个优化的方式,需要确保数组包含了所有外部作用域中会发发生变化,且在effect中使用的变量。如果只想运行一次effect,可以传递一个空数组作为第二个参数。
对于useEffect的初步认识只需要了解上面的即可。接下来就来介绍一个官网的实例,来说明useEffect
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
在上面的这段代码中,就使用到了useEffect这个hook,在每次count值改变时,就会在页面中去打印“You clicked ${count} times”这段文字,当然count肯定对应的就是其所对应的值。
useEffect去取代calss中的生命周期函数的方式
react中有状态组件中,其生命周期函数的各个阶段
- 在Mounting阶段
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
- Updating
- static getDerivedStateFormProps
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- UnMouting
- componentWillUnmount()
- componentWillUnmount()
使用hook去代替生命周期函数的方式
这里就介绍了关于useEffect这个hook的使用,有一些生命周期函数就是通过该hook来实现的,这里推荐一篇文章https://blog.logrocket.com/guide-to-react-useeffect-hook/,可以参考下。这里是在参考了一些文章后写的,具体介绍如下:
constructor: 可以通过useState来初始化state
componentDidMount(),在hook中需要使用下面的这种方式去取代,在useEffect中传递第二个参数,该参数为一个空数组,只会去执行一次,如下面所示
useEffect(() => { },[])
componentDidUpdate(),有两种方式去解决
在每次渲染的时候都去调用hooks,解决的方式如下面所示
useEffect(() => { })
用一个特殊变量的去触发hook,如下面所示,count指的就是这个特殊的变量,该hook触发,只会是count的值改变时
useEffect(() => { },[count])
componentWillUnmount(),用hook来代替,需要去return一个callback(回调函数),如下面的形式所示
useEffect(() => { return () => { //执行的为componentWillUnmount } },[])
shouldComponentUpdata(),常使用React.memo来代替,在默认情况下,它将对props对象中的复杂对象进行浅层比较,如果想要去控制比较,可以去提供一个自定义的比较函数作为第二个参数。代替hook的方式如下所示
import React from 'react' function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */ } const Weather = ({weather}) => { return (<div> <p>{weather.city}</p> <p>{weather.temperature}</p> {console.log('Render')} </div> ) } export default React.memo(Weather, areEqual)
自定义hook
通常在实际的项目开发中少不了使这种自定义的hook,前提是在整个项目中使用了hook的情况下。通常情况下就是去使用useState,useEffect这种系统已经定义好的hook去实现,在调用时你就可以直接调用当你自定义好的hook来实现你所需要的功能。下面就以自定义useReducer这个hook为例
import React, { useEffect } from 'react' function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action) { const nextState = reducer(state, action); setState(nextState); } return [state, dispatch]; } export default useReducer
在上面的这个实际例子中,我们封装了一个自定义的useReducerhook,我们可以调用这个hook去完成与reducer一样的功能了,在调用是就需要我们去传入两个参数,一个就是reducer,另外一个就是initialState,然后就能够取得state,以及dispatch方法。注意这里的返回值使用的是一个数组,这样的好处就是我们在获取其返回值时,可以采用数组结构这种方式来获取。具体关于数组的结构可以去看看es6中的部分,就能够明白了。那么接下来就是使用这个自定义好的useReducer。使用方式如下
import useReducer form '你封装useRecuer的组件中' function Todos() { const todosReducer = ( state, dispatch) => { if(dispatch.type == "") { //type值为什么时去执行 const newState == "" //执行一些操作,去更新state return newState //返回新的neState } } const [todos, dispatch] = useReducer(todosReducer, []); function handleAddClick(text) { dispatch({ type: 'add', text }); } return ( <div></div> ) }
这里并没有把实际的使用情况给写完,剩余的可以自己去补充,其使用方式就和redux的使用方式相同。这就是整个自定义hook以及去使用的过程,在实际的开发中可以去体验体验。
额外的hook
useReducer,能给那些会出发深更新的组件做性能优化,因为可以向子组件去传递dispatch而不是回调
useReducer这个hook的封装,整个封装的方法如下:
//reducer hook封装 import { useState } from 'react'; export default useReducer function(reducer, initialState) { const [state, setState] = useState(initialState); function dispatch(action){ const nextState = reducer(state, action); return setState(nextState); } return [state, dispatch] } //实际例子使用 import useReducer from ''; const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } return ( <div> Count: {state.count} <button onClick={() => dispatch({type: 'devrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </div> )
useReducer的惰性初始化,可以选择惰性地创建初始化state。因此需要设置一个初始化函数作为useReducer的第三个参数传入,这样初始化state将设置为init(initialArg),如下所示,就是一个实际的案例在useReducer中去传递第三个参数
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
注意:如果reducer hook的返回值与当前state相同,react将跳过子组件的渲染及副作用的执行
useCallback
返回值:返回一个memoized回调函数,该回调函数仅在某给依赖项改变时才会更新。
含义:把内联回调函数及其依赖项数组作为参数传入useCallback,它将返回该回调函数传递给经过优化的并使用引用相等性去避免非必要渲染
useCallBack(fn, deps)相当与useMemo(() => fn,deps)
useMemo
使用方式:const memoziedValue = useMemo(() => computeExpensiveValue(a,b), [a, b])
返回值:返回一个memoized值,把创建函数和依赖项数组作为参数传入useMemo,仅在某个依赖项改变时才重新计算memoized值。
好处:这种优化有助于避免在每次渲染时都进行高开销的计算
渲染方式:传入useMemo的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作,如属于useEffect中的副作用。如果没有,那么新的值将会在每次渲染时被重新渲染
注意:依赖项数组不会作为参数传递给函数,概述来说,就是每一个出现在函数中的参数也应该出现在依赖项的数组中
useRef
使用方式: const refContainer = useref(initialValue);
返回值:返回一个可ref对象,其.current属性被初始化为传入的参数(initialValue)。这返回的的对象将在组件的整个生命周期中持续
含义: useRef就像是一个盒子可以将.current中得可变属性给保存起来
ref与useRef的区别在于,后者是创建的了一个普通的js对象,useRef和自建一个{current: …。}对象的唯一区别是,useRef会在每次渲染时,返回同一个ref对象
useImperativeHandle
作用:可以在使用ref时自定义暴露给赋组件的实例值,使用的形式如下:
useImperativeHandle(ref, createHandle, [deps])
useLayoutEffect
更新时机:在浏览器执行下一次绘制前去执行
与useEffect相同,会在所有的DOM变更之后同步调用effect
useDebugValue
作用:在react devTools中常被用于去当作展示标签,作为客户端的钩子
hooks中的性能优化
在hook中,其性能优化的点很多,这个可以在一些https://react.docschina.org/docs/hooks-faq.html#performance-optimizations去学习,下面是我看的一部分。
如何在更新时去跳过effect,可以采用条件式方式,即在useEffect中去传递第二个参数
由于某些原因,无法将一个函数移动到effect内部时,可采用下面方式
- 尝试将函数移动到当前组件的外部
- 如果所调用对策方法是一个纯计算等,此时可以在effect外面去写这个函数
- 如果要增加一个函数去依赖项,那么要明确使用useCallback外部的hook,如下面的例子所示
function ProductPage({ productId }) { // Wrap with useCallback to avoid change on every render const fetchProduct = useCallback(() => { // ... Does something with productId ... }, [productId]); // All useCallback dependencies are specified return <ProductDetails fetchProduct={fetchProduct} />; } function ProductDetails({ fetchProduct }) { useEffect(() => { fetchProduct(); }, [fetchProduct]); // All useEffect dependencies are specified // ... }
实现shouldComponentUpdate的方式
const Button = React.memo((props) => { // your component });
如上面所示,这种实现方式并不是使用了hooks,它相当于纯组件,但是仅仅能够比较的是props。可以去增加第二个参数,采用一种函数的方式去拿到新老的props,如果结果返回true,就跳过更新阶段
记住计算结果的方式
使用useMemo这个hook去记住之前的计算结果,从而在多个渲染之中缓存计算
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
上面的代码会调用computeExpensiveValue(a,b)这个函数,但是它们依赖的a,b没有改变,那么useMemo在直接去返回上一次结果的值
结语
对于hook的学习大概就如上面所说,对于hook其中的内容还很多所以对于hook的学习最好是去官网看看,链接如下https://react.docschina.org/docs/hooks-intro.html在官网中介绍的更加详细,这里的中文文档和英文文档内容都一样,不过对于英文好的同学建议看看英文版本。
下一篇:js实现菜单跳转效果