React Hooks学习笔记

609 阅读6分钟

image.png 如果你和我一样都是React新手,相信这篇文章的内容可以帮助你学习React Hooks,详细介绍了每个Hooks的作用和使用方法,以及使用案例。欢迎收藏、点赞、转发哦~

1 React Hooks的作用:

  • 对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力。
  • 让开发者在不使用类组件的情况下,实现相同的功能。

副作用: 组件中只要不是把数据转换成视图的代码都属于副作用,比如获取DOM元素,为DOM元素添加事件,ajax请求,设置定时器等,在类组件中我们通常使用生命周期函数来处理副作用,但在函数组件中使用hooks来处理副作用。

2 类组件的不足

  • 缺少逻辑复用机制

为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显示十分臃肿;增加调试的难度;运行效率降低;

  • 类组件经常会变得很复杂难以维护

将一组想干的业务逻辑拆分到了多个生命周期函数中,在一个生命周期函数内存在多个不相干的业务逻辑。

  • 类成员方法不能保证this指向的正确性

需要使用bind和函数嵌套函数的方式来保证this指向,复杂而难以维护

3 React Hooks使用

React Hooks就是一堆钩子函数,”钩入“React特性的函数。React通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能。

3.1 useState

作用:用于为函数组件引入状态,使函数型组件可以保存状态。原本一个函数里面的变量,在函数执行后就会被释放,useState方法内部是用闭包来实现保存状态数据的。
使用细节:

  1. 接收唯一的参数既状态初始值,初始值可以是任意数据类型
  2. 返回值为数组,数组中存储状态值和变更状态值的方法。方法名称约定以set开头,后面加上状态名称。
  3. 方法可以被调用多次,用以保存不同状态值。
  4. 参数可以是一个函数,函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始状态值是动态的值的情况。
    import React, { useState } frrom 'react';
    function App(props) {
        const [ count, setCount ] = useState(() => {
            return props.count || 0 // 函数只会被调用一次
        }) 
        const [ person, setPerson ] = useState({name: '张三', age: 20})
        return (
            <div>
                <span>{count} {person.name} {person.age}</span>
                <button onclick={() => setCount(count + 1)}>+1</button>
                <button onclick={() => setPerson({...person, name: '李四'})}>setPerson</button>
            </div>
        )
    }
    export default App
    

设置状态值方法的参数可以是一个值也可以是一个函数
设置状态值方法的方法本身是异步的

3.2 useReducer()

作用:另一种让函数组件保存状态的方式
使用细节:和redux的使用类似

import React, { useReducer } from 'react'

function App() {
    function reducer (state, action) {
        switch (action.type) {
            case 'increment':
                return state + 1;
            case 'decreament':
                return state - 1;
            default:
                return state;
        }
    }
}

const [ count, dispatch ] = useeReducer(reducer, 0)
return (
    <div>
        <button onclick={() => dispatch({type: 'decrement'})}>-1</button>
        <span>{count}</span>
        <button onclick={() => dispatch({type: 'increment'})}>-1</button>
    </div>
)

export default App;
3.3 useContext()

作用:在跨组件层级获取数据时简化获取数据的代码

import React, { createContext, useContext } from 'react'

const countContext = createContext();

function App() {
    return <countContext.Provider value={100}>
        <Foo/>
    </countContext.Provider>
}

function Foo() {
    const value = useContext(countContext);
    return <div>我是Foo组件{value}</div>
}

export default App
3.4 useEffect()

作用:让函数组件拥有处理副作用的能力,类似生命周期函数

  1. useEffect执行时机 可以把useEffect看做componentDidMount,componentDidUpdate, componentWillUnmount这三个函数的组合
    useEffect(() => {}) ---> componentDidMount, componentDidUpdate
    useEffect(() => {}, []) ---> componentDidMount
    useEffect(() => () => {}) ---> componentWillUnMount
  2. useEffect 使用方法
import React, { useEffect, useState } from "react"
import ReactDom from 'react-dom'

function App() {
    function onScroll () {
        console.log('页面发生滚动了')
    }
    
    // 组件挂载之后监听页面滚动,组件卸载之后,滚动时间被解绑
    useEffect(() => {
        window.addEventListener('scrroll', onScroll)
        return () => {
            window.removeEventListener('scroll', onScroll)
        }
    }, []) 
    
    return <div>
        <span>{count}</span>
        <button onclick={() => ReactDom.unmountComponentAtNode(document.getElementById('tooy')}>卸载组件</button>
    </div>
}

export default App
  1. useEffect 解决的问题:
    (1)按照用途将代码进行分类(将一组相关的业务逻辑归置到了同一个副作用函数中)
    (2)简化重复代码,使组件内部代码更加清晰。

  2. 使用useEffect的第二个参数来实现,只有指定数据发生变化时触发effect。

  3. useEffect 结合异步函数
    useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise,可以通过添加自执行函数的方式来结合异步函数。

useEffect(() => {
    (async () => {
        await axios.get()
    })()
})
3.5 useMemo()

作用: 类似于Vue中的计算属性,可以监测某个值的变化,根据变化值计算新值。
useMemo会缓存计算结果,如果监测值没有发生变化,即使组件重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算。

import React, { useState, useMemo } from 'react'

function App() {
    const [count, setCount] = useState(0)
    const result = useMemo(() => {
        // 如果count值发生变化此函数重新执行
        return count * 2
    }, [count])
    return (
        <div>
            <span>{count} {result}</span>
            <button onClick={() => setCount(count + 1)}>+1</button>
        </div>
    )
}

export deefault App
3.6 memo方法

作用:性能优化,如果本组件的数据没有发生变化,阻止组件更新,类似类组件中的PureComponent和ShouldComponentUpdate

import React, { useState, memo } from "react"

function App() {
    const [count, setCount] = useState(0)
    return (
        <div>
            <span>{count}</span>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <Foo />
        </div>
    )
}

const Foo = memo(function Foo() {
    // memo包裹后,count发生变化,App组件重新渲染,但是子组件中的数据并未发生变化所以Foo不会被重新渲染
    console.log("Foo组件重新渲染了")
    return <div>我是Foo组件</div>
})

export default App
3.7 useCallback()

作用:性能优化,缓存函数,使用组件重新渲染时得到相同的函数实例。从而避免嵌套函数的再次渲染。

import React, { useState, memo, useCallback } from "react"

function App() {
    const [count, setCount] = useState(0)
    const resetCount = useCallback(() => setCount(0), [setCount])
    return (
        <div>
            <span>{count}</span>
            <button onClick={() => setCount(count + 1)}>+1</button>
            <Foo resetCount={resetCount} />
        </div>
    )
}

const Foo = memo(function Foo(props) {
    // memo包裹后,count发生变化,App组件重新渲染,但是子组件中的数据并未发生变化所以Foo不会被重新渲染
    console.log("Foo组件重新渲染了")
    return (
        <div>
            我是Foo组件
            <button onClick={props.resetCount}>resetCount</button>
        </div>
    )
})

export default App
3.8 useRef()

作用1:获取DOM元素对象

import React, { useRef } from 'react'

function App () {
    const username = useRef()
    const handler = () => console.log(username) // { current: input}
    return <input ref={username} onChange={handler} />
}

exporrt default App

作用2:保存数据(跨组件周期)
即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染

import React, { useState, useEffect, useRef } from 'react'

function App() {
    const [count, setCount] = useState(0)
    let timerId = useRef() // 如此可以避免count 变化时,App组件重新渲染,timerId丢失而无法清除定时器的问题
    useEffect() => {
        timerId.current = setInterval(() => {
            setCount(count => count + 1)
        }, 1000)
    }, [])
    const stopCount = () => {
        console.log(timerId)
        clearInterval(timerId.current)
    }
    return (
        <div>
            {count}
            <button onClick={stopCount}>停止</button>
        </div>
    )
}

export default App

更多关于useRef的介绍请查看useRef详细总结

3.9 路由钩子函数
import React from 'react'
import { useHistory, useLocation, useRouteMatch, useParams } from 'react-router-dom'

export default function Home(props) {
    console.log(props)
    console.log(useHistory())
    console.log(useLocation())
    console.log(useRouteMatch())
    console.log(useParams())
    return <div>Home Workd</div>
}

image.png image.png

4 自定义钩子函数

自定义Hook是标准的封装和共享逻辑的方式
自定义Hook是一个函数,起名称以use开头
自定义Hook其实就是逻辑和内置Hook的组合

import React, { useState, useEffect } from 'react'
import axios from 'axios'

function useGetPost () {
    const [post, setPost] = useState({})
    useEffect(() => {
        axios.get('https://test.com/post/1')
            .then(responsee => setPost(response.data))
    }, [])
    return [post, setPost]
}

function App() {
    const [post, setPost] = useGetPost()
    return (
        <div>
            <div>{post.title}</div>
            <div>{post.body}</div>
        </div>
    )
}

export default App

image.png