Hook学习

168 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

hook

什么是hook

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数

useState

useState和useReduce 作为能够触发组件重新渲染的hooks,我们在使用useState的时候要特别注意的是,useState派发更新函数的执行,就会让整个function组件从头到尾执行一次,所以需要配合useMemo,usecallback等api配合使用

useState 函数返回 一个state状态(变量)和一个改变状态的函数
useState(0) 参数为state状态的初始值。

    const [count,setCount] = useState(18);
    //此时 count = 18;

setCount 和 类组件中的setState的区别:setState合并新旧statesetCount不会

useEffect

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API

    //相当于componentDidMount 和componentDidUpdata
    useEffect({
        //逻辑操作
    })

useEffect Hook 看作 componentDidMount componentDidUpdate 和 componentWillUnmount 这三个函数的组合
如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它
方便取消订阅等在componentWillUnMount的清除操作

    useEffect(()=>{
        //订阅
        obj.subscrible();
        return ()=>{
            //取消订阅 
            obj.unsubscrible()
        }
    })

useEffect 如果不添加限制调教,每次state更新或者props更新时都会重新调用Effect函数,相当于componentDidUpdatecomponentwillreceiveprops

uesEffect添加合理的限制:通过useEffect的第二个参数来实现。

这里说是限定条件,也可以说是上一次useeffect更新收集的某些记录数据变化的记忆,在新的一轮更新,useeffect会拿出之前的记忆值和当前值做对比,如果发生了变化就执行新的一轮useEffect的副作用函数,useEffect第二个参数是一个数组,用来收集多个限制条件 。(引用自:我不是外星人)

当useEffect函数的第一个参数中的变量发生改变时就调用该useEffect的副作用函数
  • 当为空数组即相当于时componentDidMounted

useEffect 不支持async 和 await 的语法糖

使用 async 需要在useEffect外包一层函数

错误用法 useEffect中的第一个回调参数返回的是一个clean-up函数,所以不能返回promise对象,更不能直接使用async/await

    //错误用法
    useEffect(async ()=>{
        await ...
    },[])

包裹一层函数

    const asyncEffect = (callback, deps)=>{ 
            useEffect(()=>{ 
                await callback() 
            },deps) 
        }

在回调函数内部使用立即执行函数

    useEffect(()=>{
        //立即执行函数async function fn(){
            await ...
         })
    },[])

在回调函数外部定义一个async函数,在回调函数内部调用该函数

useEffect 执行顺序 组件更新挂载完成 -> 浏览器dom 绘制完成 -> 执行useEffect回调 useLayoutEffect 执行顺序 组件更新挂载完成 -> 执行useLayoutEffect回调-> 浏览器dom 绘制完成

useLayoutEffect 会阻塞浏览器的渲染


useRef

获取元素

    const dom = useRef();
    //通过dom元素的ref标签获取dom元素
    <h1 ref={dom}>
    //通过dom.current能获取到该元素

缓存数据(高阶用法)

优势:不会像useState那样数据改变更新组件,也不会像定义变量一样,更新就重置

    //初始数据
    let ref = useRef(initValue)
    //改变数据
    ref.current = newValue

解决hook的异步问题

useRef 创建一个不受组件刷新而影响的变量。同高阶用法相同

useContext

useContext

通过useContext可以获取到父组件最近的一个Provider的value

useContext 可以代替 context.Consumer 来获取Provider中保存的value值

Context Provider的value发生变化是,他的所有子级消费者都会rerender

简化代码,不需要用用消费者组件包裹消费组件

让你不使用组件嵌套就可以订阅 React 的 Context

不使用useContext

    let Mycontext = React.createContext();
    //生产数据
    <Mycontext.Provider value={...data}>
        父组件
        <Father>
            .....
            <Mycontext.Consumer>
            {   //基于context的值进行渲染进行渲染
                value =>(
                )
            }
            </Mycontext.Comsumer>
        </Father>
    <\Mycontext.Provider>

使用useContext

可以不使用消费组件,获取context的数据

    const value = useContext(Mycontext)

useReducer

reducer

reducer 是一个函数`(state,action)=>{}

state传入的状态,action触发状态的更新操作

reducer是一个纯函数

state

reducer处理的state对象必须是immutable,这意味着永远不要直接修改参数中的state对象,reducer函数应该每次都返回一个新的state object

既然reducer要求每次都返回一个新的对象,我们可以使用ES6中的解构赋值方式去创建一个新对象,并复写我们需要改变的state属性

当个state是一个多重嵌套的对象时,使用immer等immutable库处理、

action

一个常规的action对象,有两个属性,typepayload(可选)

type:本次操作的类型,reducer条件判断的依据

payload:(可选)本次操作携带的数据

useReducer

    const [state, dispatch] = useReducer(reducer, initState);

useReducer接受两个参数,第一个参数reducer函数,第二个参数state的初始值

useReducer返回一个数组[state,dispatch]

state: 为的新的state,

dispatch: 用来触发reducer函数,dispatch的参数为action对象

    function demo(){
        const initalValue = {
            nickName:'hacker',
            age:18
        }
        function myReducer = (state,action){
            switch (action.type){
                case 'grow' : 
                    return {...state,age:state.age++};
                case 'editNickName' :
                    return {...state,nickName:state.nickName=action.Nickname};
                default: 
                    return state;
            }
        }
        const [state,dispatch] = useReducer(myReducer,initalValue);
        
        //调用dispatch 修改state state 改变组件重新render
        dispatch({
            type:'editNickName',
            payload:{
                nickName:'jack'
            }
       })
    }
   
    

useREducer+useContext

如何利用context去解决我们问中开头提到的子孙类组件出发reducer状态变化。没错,就是将dispatch函数作为context的value,共享给页面的子组件。

    const MyContext = useContext(initalValue)
    //将`dispatch函数`作为`context的value`,共享给页面的子组件。
    <MyContext.Provider value= {dispatch}>
    ....
    </<MyContext.Provider>
    
    function Children(){
          const dipatch = useContext(MyContext);
          //使用dispatch
    }

useMemo

无状态组件的更新是整个全部更新,使用useMemo可以实现一部分视图更新,从而达到性能优化

Memo

Memo是一个高阶函数,作用和类组件中的React.pureCmponent类似,用于函数组件

引用:我们先来说一说, memo, 我们知道class声明的组件可以用componentShouldUpdate来限制更新次数,那么memo就是无状态组件的ShouldUpdate , 而我们今天要讲的useMemo就是更为细小的ShouldUpdate单元

React.memo()可接受2个参数,第一个参数为纯函数的组件,第二个参数用于对比props控制是否刷新,与shouldComponentUpdate()功能类似。[2]

引用自简书

import React from "react";

function Child({seconds}){
    console.log('I am rendering');
    return (
        <div>I am update every {seconds} seconds</div>
    )
};

function areEqual(prevProps, nextProps) {
    if(prevProps.seconds===nextProps.seconds){
        return true
    }else {
        return false
    }

}
export default React.memo(Child,areEqual)

useMemo

useMemo 接受两个参数,一个是回调函数,另一个是依赖项。

当依赖项发生改变时(即依赖的数据发送改变时),会触发回调函数的调用,从而获取到回调函数的新的返回值 当依赖项没有发生改变时,就会返回之前的缓存值

    const MemoDemo = useMemo(()=>{
        .......
    },[])
    const info = useMemo(()=>{
        return {
            name:'',
            age:count
        }
    })
    <UserInfo userInfo={info}/>

useCallback

和useMemo类似,接受两个参数

不同的是userMemo返回的是函数的返回值,useCallback返回的是函数本身

当依赖项发生改变时,返回一个新的函数。

props传入的回调函数在父组件发生更新时,会生成一个新函数。

    const handleClick = useCallback(() => {
        setCount(count + 1) 
    }, [count]);