
React Hook 指南
什么是 Hook ?
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook本质上就是一个函数,它简洁了组件,有自己的状态管理,生命周期管理,状态共享。
useStateuseEffectuseContextuseReducer
Hook 出现解决了什么 ?
- 组件之间状态复用, 例如:使用useContext 可以很好的解决状态复用问题,或者自定义
Hook来定制符合自己业务场景遇到的状态管理。- 在函数组件中 生命周期的使用,更好的设计封装组件。在函数组件中是不能直接使用生命周期的,通过
Hook很好的解决了此问题。- 函数组件与 class 组件的差异,还要区分两种组件的使用场景。 使用
Hook完全不用去想这些,它可以使用更多React新特性。
什么时候使用 Hook ?
在函数组件顶层调用
在 函数中使用 / 自定义
Hook中使用
React 内置的 Hook
useState状态管理
useEffect生命周期管理
useContext共享状态数据
useMemo缓存值
useRef获取Dom 操作
useCallback缓存函数
useReducerredux 相似
useImperativeHandle子组件暴露值/方法
useLayoutEffect完成副作用操作,会阻塞浏览器绘制
useState 状态管理
在
class组件中,我们获取state是 通过this.state来获取的。而在
函数组件中, 是没有this的, 我们可以使用Hook提供的useState来管理和维护state.
useState 定义 / 使用
const [state, setState] = useState(initialState)
setState 为更新 satate 方法useState(initialState) initialState 为初始值
完整栗子
1import {useState} from 'react'; 2 3export default () => { 4 const [data, setData] = useState('微信公众号: 前端自学社区') 5 return ( 6 <div> 7 <h1>{data}</h1> 8 {/* 更新 state */} 9 <button onClick={()=>{setData('微信公众号: 前端自学社区 666')}}></button> 10 </div> 11 ) 12}
useEffect 生命周期管理
定义
useEffect可以看作是函数式 组件的 生命周期管理。因为在 函数式组件中无法直接使用生命周期,就必须托管
Hook来进行管理使用了。
useEffect可以使用的 3 个生命周期函数:
componentDidmountcomponentDidUpdatecomponentWillUnmount
无需清除 Effect 使用
什么是无需清除
Effect使用?React 更新 DOM 之后运行一些额外的代码
那么它就是在生命周期的
compoentDidmount和componentUpdate中执行即可。
1 useEffect(() => { 2 //默认会执行 3 // 这块相当于 class 组件 生命周期的 4 //compoentDidmount compoentDidUpdate 5 }, [])
清除Effect 使用
1. 什么是 清除
Effect?当组件进行卸载时,需要执行某些事件处理时,就需要用到 class 组件生命周期的
componentUnmount.在
useEffect中很方便使用,在内部返回一个方法即可,在方法中写相应业务逻辑2. 为什么 要在
Effect中返回一个函数 ?这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
1 useEffect(()=>{ 2 return () => { 3 console.log('组件卸载时执行') 4 } 5 })
监听 state 变化
可以通过控制 监听
state变化来实现相应的业务逻辑。
1 useEffect(() => { 2 // 监听num,count 状态变化 3 // 不监听时为空 [] , 或者不写 4 }, [num, count])
完整栗子
1import { useState, useEffect } from 'react'; 2 3export default () => { 4 const [num, setNum] = useState(0) 5 const [count, setCount] = useState(1) 6 7 useEffect(() => { 8 //默认会执行 9 // 这块相当于 class 组件 生命周期的 compoentDidmount compoentDidUpdate 10 console.log(`num: ${num}`) 11 console.log(`count: ${count}`) 12 13 // 组件在卸载时,将会执行 return 中内容 14 return () => { 15 // 相当于 class 组件生命周期的 componentWillUnMount 16 console.log('测试') 17 } 18 }, [num]) 19 20 return ( 21 <div> 22 <h1>{num}</h1> 23 <button onClick={() => { setNum(num + 1) }}> 更新Num</button> 24 <hr /> 25 <h1>{count}</h1> 26 <button onClick={() => { setCount(count + 1) }}> 更新Count</button> 27 </div> 28 ) 29}
useRef
什么是 useRef ?
useRef返回的是一个可变的ref对象,它的属性current被初始化为传入的参数(initialValue),返回的ref对象在组件的整个生命周期内保持不变。作用:
获取Dom操作,例如 获取
input焦点获取子组件的实例(只有类组件可用)
在函数组件中的一个全局变量,不会因为重复 render 重复申明
栗子
1import {useRef} from 'react'; 2 3 4export default () => { 5 const inputRef = useRef({value:0}) 6 return ( 7 <div> 8 <h1>测试</h1> 9 <input type="text" ref={inputRef} /> 10 <button onClick={()=>{console.log(inputRef.current.value)}}>获取input 值</button> 11 <button onClick={()=>{inputRef.current.focus()}}>获取input 焦点</button> 12 </div> 13 ) 14}
useContext 状态数据共享
Context 解决了什么
在日常开发中,我们父子组件都是通过
props来进行通信,如果遇到跨级组件通信那么我们就不好通过props来处理了。这时候可以想想怎么可以把 组件 状态 共享出去使用?
ContextRedux.....本小节通过
Context来 达到组件数据共享
什么是 Context
数据共享,任何组件都可访问Context 数据。
在
React中,组件数据通过prop来达到 自上而下的传递数据,要想实现全局传递数据,那么可以使用Context.注意:
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。
创建 Context
在使用
Context前提,必须创建它,可以为它单独创建一个文件来管理Context,
1import React from 'react'; 2 3export const MyContext = React.createContext(); 4
使用 Context
在使用
Context时,它通常用在顶级组件(父组件上),它包裹的内部组件都可以享受到state的使用和修改。通过
Context.Provider来进行包裹,值通过value = {}传递。子组件如何使用
Context传递过来的值 ?
- 通过
useContext()Hook 可以很方便的拿到对应的值.
1// Context.js 2import React from 'react'; 3 4export const MyContext = React.createContext();
1import { useContext } from 'react'; 2import {MyContext} from '../Context/index' 3 4const result = { 5 code:200, 6 title:'添加数据成功' 7} 8const Son = () => { 9 const res = useContext(MyContext) 10 return ( 11 <> 12 <div> 13 <h1>{res.code}</h1> 14 <hr/> 15 <h2>{res.title}</h2> 16 </div> 17 </> 18 ) 19} 20 21 22export default () => { 23 return ( 24 <MyContext.Provider value={result}> 25 <div> 26 <h1>前端自学社区</h1> 27 <Son/> 28 </div> 29 </MyContext.Provider> 30 ) 31} 32 33
useMemo 提升性能优化
定义
useMemo用于性能优化,通过记忆值来避免在每个渲染上执⾏高开销的计算。
useMemo参数:
useMemo返回值是memoized值,具有缓存作用array控制useMemo重新执⾏的数组,array 中 的 state 改变时才会 重新执行useMemo注意:
- 不传数组,每次更新都会重新计算
- 空数组,只会计算一次
- 依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个 useMemo 返回的值)
栗子
1import { useState, useMemo} from 'react'; 2 3 4export default () => { 5 const [count, setCount] = useState(0) 6 const [num, setNum] = useState(0) 7 const newValue = useMemo(()=>{ 8 console.log(`count 值为${count}`) 9 console.log(`num 值为 ${num}`) 10 return count+num 11 },[count]) 12 return( 13 <div> 14 <h1>{count}</h1> 15 <button onClick={()=>{setCount(count+1)}}>count + 1</button> 16 <hr/> 17 <h1>{num}</h1> 18 <button onClick={()=>{setNum(num+1)}}>Num + 1</button> 19 <hr/> 20 <h2>{newValue}</h2> 21 </div> 22 ) 23}
解析栗子
当点击了 5 次更新
num值,页面中newValue的值始终显示为 0,这是为什么呢?因为我在
useMemo监听记录的是count的值,当count值发生变化时,页面上的newValue在会重新计算,虽然你点击了 5 次 更新num,页面没有更新,但是已经缓存起来了,当点击 更新count时,它会 计算count+1 的值 和 num 缓存的值, 最终结果 为 5。减少了计算消耗。
useCallback 提升性能优化
定义
useCallback可以说是useMemo的语法糖,能用useCallback实现的,都可以使用useMemo, 常用于react的性能优化。
useCallback的参数:
callback是一个函数用于处理逻辑array控制useCallback重新执⾏的数组,array改变时才会重新执⾏useCallback
使用
它的使用和
useMemo是一样的,只是useCallback返回的函数。
1import { useState, useCallback} from 'react'; 2 3 4export default () => { 5 const [count, setCount] = useState(0) 6 const [num, setNum] = useState(0) 7 const newValue = useCallback(()=>{ 8 console.log(`count 值为${count}`) 9 console.log(`num 值为 ${num}`) 10 return count+num 11 },[count]) 12 return( 13 <div> 14 <h1>{count}</h1> 15 <button onClick={()=>{setCount(count+1)}}>count + 1</button> 16 <hr/> 17 <h1>{num}</h1> 18 <button onClick={()=>{setNum(num+1)}}>Num + 1</button> 19 <hr/> 20 {/* 调用useCallback 返回的值 */} 21 <h2>{newValue()}</h2> 22 </div> 23 ) 24}
小结
useMemo和useCallback功能类似,都是提升性能优化。该采用哪种方式来最佳实践,还有待探索。
欢迎 读者 与 我交流。
<br/>网上对
useMemo和useCallback的看法 ?
useCallback如果在函数式组件中的话,确实应该当作最佳实践来用,但是使用它的目的除了要缓存依赖未改变的回调函数之外(与 useMemo 类似),还有一点是为了能够在依赖发生变更时,能够确保回调函数始终是最新的实例,从而不会引发一些意料之外的问题,我感觉后者才是使用 useCallback 的出发点,而非缓存。因为你想啊,即使不用 useCallback,假设这个回调函数也没有任何依赖状态,我直接把这个函数声明在组件外部不也可以吗?我直接使用 ref 不是更自由吗?<br/><br/>
useMemo本身名字就是和缓存有关联的,本质上就为了解决一个事情,在 render 里面不要直接创建对象或者方法什么的,因为组件每渲染一次,就会创建一次(比如 style 或者一些常量状态),造成不必要的资源浪费。理想情况应当是,如果存在依赖,只在依赖变化时重新创建,不存在依赖,那就只创建一次。表面上看,如果所有状态都用 useMemo,肯定没什么问题,但你还需从缓存的代价上来分析这个问题,如果使用 useMemo 缓存一个状态的代价大于它带来的优势,那是不是反而适得其反了?大家对
useMemo和useCallback有何看法,欢迎在下方评论或者加我讨论。
useImperativeHandle
定义
useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用ref这样的命令式代码。useImperativeHandle应当与forwardRef一起使用。
useImperativeHandle作用 :子组件可以暴露给父组件 实例使用
格式: useImperativeHandle(ref,()=>{},[])
参数1: 子组件向父组件暴露的实例
参数2: 函数,传递的父组件可操作的实例和方法
参数3: 监听状态,更新状态
1 2 3import {useState,useImperativeHandle, forwardRef,useRef} from 'react'; 4 5 6const Son = forwardRef( (props,ref) => { 7 const inputRef = useRef(0) 8 const domRef = useRef() 9 const [state, setState] = useState('等待') 10 useImperativeHandle(ref,()=>({ 11 focus:() => {inputRef.current.focus()}, 12 domRef 13 })) 14 return ( 15 <div> 16 <h1>{state}</h1> 17 <hr/> 18 <input type="text" ref={inputRef}/> 19 <h2 ref={domRef}>测试---------useImperativeHandle</h2> 20 </div> 21 ) 22}) 23 24 25export default () => { 26 const refFather = useRef(0) 27 return ( 28 <div> 29 <h1>父组件</h1> 30 <Son ref={refFather} /> 31 <button onClick={()=>{refFather.current.focus()}}>获取子组件实例------获取input焦点</button> 32 <button onClick={()=>{console.log(refFather.current.domRef.current.innerHTML)}}>获取子组件实例------获取h2 Dom</button> 33 </div> 34 ) 35}
useReducer
定义
它是
useState的替代方案。它接收一个形如(state, action) => newState的reducer,并返回当前的state以及与其配套的dispatch方法。如果熟悉
Redux使用的话,用useReducer就是轻车熟路了,发车了。
使用Reducer实现一个加减器
1import {useReducer} from 'react'; 2 3 4export default () => { 5 const [state, dispatch] = useReducer((state,action)=> { 6 switch (action.type){ 7 case 'addNum': 8 return { 9 num:state.num+1 10 } 11 case 'subtractNum': 12 return { 13 num:state.num-1 14 } 15 } 16 17 },{ 18 num:0 19 }) 20 return ( 21 <div> 22 <h2>{state.num}</h2> 23 <button onClick={()=>{dispatch({type:'addNum'})}}> 增加num</button> 24 <button onClick={()=>{dispatch({type:'subtractNum'})}}> 减num</button> 25 </div> 26 ) 27}
结语
❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章<br/><br/>关注公众号 “前端自学社区”,即可获取更多前端高质量文章!<br/><br/>关注后回复关键词“加群”, 即可加入 “前端自学交流群”,共同学习进步。<br/><br/>关注后添加我微信拉你进技术交流群<br/><br/>欢迎关注公众号,更多精彩文章只在公众号推送

