react-hooks基础学习

97 阅读10分钟

1.什么是Hook?

  • Hook是React 16.8的新增特性
  • 可以让函数式组件拥有类组件特性

react-hooks

2.为什么需要Hook?

  • 在Hook出现之前, 如果我们想在组件中保存自己的状态, 如果我们想在组件的某个生命周期中做一些事情, 那么我们必须使用类组件
    • 但是类组件的学习成本是比较高的, 你必须懂得ES6的class, 你必须懂得箭头函数
    • 但是在类组件的同一个生命周期方法中, 我们可能会编写很多不同的业务逻辑代码 这样就导致了大量不同的业务逻辑代码混杂到一个方法中, 导致代码变得很难以维护 (诸如: 在组件被挂载的生命周期中, 可能主要注册监听, 可能需要发送网络请求等)
    • 但是在类组件中共享数据是非常繁琐的, 需要借助Context或者Redux等
  • 所以当应用程序变得复杂时, 类组件就会变得非常复杂, 非常难以维护
  • 所以Hook就是为了解决以上问题而生的

3.如何使用Hook?

  • Hook的使用我们无需额外安装任何第三方库, 因为它就是React的一部分
  • Hook只能在函数组件中使用, 不能在类组件,或者函数组件之外的地方使用
  • Hook只能在函数最外层调用, 不要在循环、条件判断或者子函数中调用

2.Hooks使用

useState

  • 参数:保证状态的初始值
  • 返回值: 数组(两个元素)
    • 第一个元素:保存的状态
    • 第二个元素:修改保存状态的方法

注意点:在同一个函数式组件中,可以多次使用同名的Hook

import React, { useState } from "react"
function About() {
    const arr = useState(777)
    const [list, setList] = useState([
        {
            id: 1, name: '花花'
        },
        {
            id: 2, name: '萌兰'
        },
        {
            id: 3, name: '飞云'
        }
    ])
    const [state, setState] = arr
    return (
        <div>
            <p>{state}</p>
            <button onClick={() => setState(state + 1)}>+1</button>
            <button onClick={() => setState(state - 1)}>-1</button>
            <ul>
                {
                    list.map(item => {
                        return <li>{item.name}</li>
                    })
                }
            </ul>
        </div>
    )
}
export default About

useEffect

  • 功能

可以把useEffect Hook看做 componentDidMount、componentDidUpdate和componentWillUnmount三个周期函数的组合

  • 特点
    • 可以设置依赖,只有依赖发生变化的时候才会执行
    • 对比类组件优势,易于拆分
import React, { useEffect, useState } from "react"
function About() {
    const arr = useState(777)
    const [state, setState] = arr
    // useEffect(() => {
    //     console.log('组件被挂载或者组件更新完成')
    //     return () => {
    //         console.log('组件即将被卸载')
    //     }
    // })
    useEffect(() => {
        console.log('修改DOM')
    })
    useEffect(() => {
        // 组件被挂载
        console.log('注册监听')
        return () => {
            console.log('移出监听')
        }
    })
    useEffect(() => {
        console.log('发送网络请求')
    })
    return (
        <div>
            <p>{state}</p>
            <button onClick={() => setState(state + 1)}>+1</button>
            <button onClick={() => setState(state - 1)}>-1</button>
        </div>
    )
}
export default About

useContext

useContext相当于 类组件中的 static contextType = Context

import React, { createContext, useContext, useState } from "react"
const UserConetext = createContext({})
const ColorContext = createContext({})
function ASon() {
    const user = useContext(UserConetext)
    const color = useContext(UserConetext)
    return (
        <div>
            <p>{user.name}</p>
            <p>{user.age}</p>
            <p>{color.color}</p>
        </div>
    )
}
function About() {
    const arr = useState(777)
    const [state, setState] = arr

    return (
        <div>
            <p>{state}</p>
            <button onClick={() => setState(state + 1)}>+1</button>
            <button onClick={() => setState(state - 1)}>-1</button>
            <UserConetext.Provider value={{ name: 'ss', age: state }}>
                <ColorContext.Provider value={{ color: 'red' }}>
                    <ASon />
                </ColorContext.Provider>
            </UserConetext.Provider>

        </div>
    )
}
export default About

useReducer

从名称来看, 很多人会误以为useReducer是用来替代Redux的, 但是其实不是。seReducer是useState的一种替代方案, 可以让我们很好的复用操作数据的逻辑代码

import React, { useState } from "react"

function ASon() {
    const [numState, setNumState] = useState(0)
    function increment() {
        setNumState(numState + 1)
    }
    function decrement() {
        setNumState(numState - 1)
    }
    return (
        <div>
            <p>{numState}</p>
            <button onClick={() => increment()}>+1</button>
            <button onClick={() => decrement()}>-1</button>
        </div>
    )
}
function BSon() {
    const [numState, setNumState] = useState(0)
    function increment() {
        setNumState(numState + 1)
    }
    function decrement() {
        setNumState(numState - 1)
    }
    return (
        <div>
            <p>{numState}</p>
            <button onClick={() => increment()}>+1</button>
            <button onClick={() => decrement()}>-1</button>
        </div>
    )
}
function About() {
    return (
        <div>
            <ASon />
            <BSon />
        </div>
    )
}
export default About

上面代码的问题:重复性代码,冗余

import React, { useState, useReducer } from "react"
function reducer(state, action) {
    switch (action.type) {
        case 'ADD':
            return { ...state, num: state.num + 1 }
        case 'SUB':
            return { ...state, num: state.num - 1 }
        default:
            return state
    }
}
function ASon() {
    const [state, dispatch] = useReducer(reducer, { num: 0 })
    return (
        <div>
            <p>{state.num}</p>
            <button onClick={() => dispatch({ type: 'ADD' })}>+1</button>
            <button onClick={() => dispatch({ type: 'SUB' })}>-1</button>
        </div>
    )
}
function BSon() {
    const [state, dispatch] = useReducer(reducer, { num: 5 })
    return (
        <div>
            <p>{state.num}</p>
            <button onClick={() => dispatch({ type: 'ADD' })}>+1</button>
            <button onClick={() => dispatch({ type: 'SUB' })}>-1</button>
        </div>
    )
}
function About() {
    return (
        <div>
            <ASon />
            <BSon />
        </div>
    )
}
export default About

useCallback

1.什么是useCallback Hook?

useCallback用于优化代码, 可以让对应的函数只有在依赖发生变化时才重新定义

  • 当前ASon和BSon重新渲染的原因是因为
  • 父组件中的数据发生了变化, 会重新渲染父组件
  • 重新渲染父组件, 就会重新执行父组件函数
  • 重新执行父组件函数, 就会重新定义increment/decrement
  • 既然increment/decrement是重新定义的, 所以就和上一次的不是同一个函数了
  • 既然不是同一个函数, 所以ASon和BSon接收到的内容也和上一次的不一样了
  • 既然接收到的内容和上一次不一样了, 所以就会重新渲染

问题一

import React, { useState } from "react"
function ASon() {
    console.log('ASON渲染')
    return (
        <div>
            <p>ason</p>
        </div>
    )
}
function BSon() {
    console.log('BSON渲染')
    return (
        <div>
            <p>bson</p>
        </div>
    )
}
function About() {
    const [num, setNum] = useState(0)
    function increment() {
        setNum(num + 1)
    }
    return (
        <div>
            <>{num}</>
            <button onClick={() => { increment() }}>增加</button>
            <ASon />
            <BSon />
        </div>
    )
}
export default About

父改变,子会重新渲染

解决:使用memo

memo

React.memo 仅检查 props 变更

官网说明

image.png

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

function ASon() {
    console.log('ASON渲染')
    return (
        <div>
            <p>ason</p>
        </div>
    )
}
function BSon() {
    console.log('BSON渲染')
    return (
        <div>
            <p>bson</p>
        </div>
    )
}
const MemoASon = memo(ASon)
const MemoBSon = memo(BSon)
function About() {
    const [num, setNum] = useState(0)
    function increment() {
        setNum(num + 1)
    }
    return (
        <div>
            <>{num}</>
            <button onClick={() => { increment() }}>增加</button>
            <MemoASon />
            <MemoBSon />
        </div>
    )
}
export default About

问题2

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

function ASon(props) {
    console.log('ASON渲染')
    return (
        <div>
            <p>ason</p>
            <button onClick={() => props.handler()}>+</button>
        </div>
    )
}
function BSon(props) {
    console.log('BSON渲染')
    return (
        <div>
            <p>bson</p>
            <button onClick={() => props.handler()}>-</button>
        </div>
    )
}
const MemoASon = memo(ASon)
const MemoBSon = memo(BSon)
function About() {
    console.log('ABOUT渲染')
    const [num, setNum] = useState(0)
    const [countState, setCountState] = useState(0)
    function increment() {
        setNum(num + 1)
    }
    function decrement() {
        setCountState(countState - 1)
    }
    
    return (
        <div>
            <div>num: {num}</div>

            <div>countState: {countState}</div>
            {/* <button onClick={() => { increment() }}>增加</button> */}
            <MemoASon handler={increment} />
            <MemoBSon handler={decrement} />
        </div>
    )
}
export default About

子改父,子未改变,但是父改变,传入的props内容改变,子全部重新渲染

解决:useCallback

useCallback

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

function ASon(props) {
    console.log('ASON渲染')
    return (
        <div>
            <p>ason</p>
            <button onClick={() => props.handler()}>+</button>
        </div>
    )
}
function BSon(props) {
    console.log('BSON渲染')
    return (
        <div>
            <p>bson</p>
            <button onClick={() => props.handler()}>-</button>
        </div>
    )
}
const MemoASon = memo(ASon)
const MemoBSon = memo(BSon)
function About() {
    console.log('ABOUT渲染')
    const [num, setNum] = useState(0)
    const [countState, setCountState] = useState(0)
    function increment() {
        setNum(num + 1)
    }
    // function decrement() {
    //     setCountState(countState - 1)
    // }
    const decrement = useCallback(() => {
        setCountState(countState - 1)
    }, [countState]) // 只有依赖数据countState改变才会重新渲染定义,否则函数decrement定义不变

    return (
        <div>
            <div>num: {num}</div>

            <div>countState: {countState}</div>
            {/* <button onClick={() => { increment() }}>增加</button> */}
            <MemoASon handler={increment} />
            <MemoBSon handler={decrement} />
        </div>
    )
}
export default About

useMemo

useMemo用于优化代码, 可以让对应的函数只有在依赖发生变化时才返回新的值

react-useMemo

import {memo, useCallback, useMemo, useState} from "react"
function ASon (prop) {
    console.log('ASon渲染');
    return (
        <div>
            <div>ASon</div>
            <button onClick={() => prop.handle()}>+</button>
        </div>
    )
}
function BSon (prop) {
    console.log('BSon渲染');
    return (
        <div>
            <div>BSon</div>
            <button onClick={() => prop.handle()}>-</button>
        </div>
    )
}
const ASonMemo = memo(ASon)
const BSonMemo = memo(BSon)
function About () {
    console.log('About渲染');
    const [num, setNum] = useState(5)
    const [aboutState, setAboutState] = useState(0)
    function increment() {
        setNum(num + 1)
    }
    // function decrement() {
    //     setAboutState(aboutState - 1)
    // }
    // const decrement = useCallback(() => {
    //     setAboutState(aboutState - 1)
    // }, [aboutState])
    const decrement = useMemo(() => {
        return () => {
            setAboutState(aboutState - 1)
        }
    }, [aboutState])
    return (
        <div>
            <div>About</div>
            <div>{num}</div>
            <div>{aboutState}</div>
            <ASonMemo handle={increment}></ASonMemo>
            <BSonMemo handle={decrement}></BSonMemo>
        </div>
    )
}
export default About

useCallback实现原理

// 伪代码
function useCallback(fn, arr){
    return useMemo(()=>{
        return fn;
    }, arr);
}

useCallback和useMemo的区别

  • useCallback返回的永远是一个函数
  • useMemo返回的是return返回的内容
import {memo, useCallback, useMemo, useState} from "react"
function ASon (prop) {
    console.log('ASon渲染');
    return (
        <div>
            <div>ASon</div>
            <button onClick={() => prop.handle()}>+</button>
        </div>
    )
}
function BSon (prop) {
    console.log('BSon渲染');
    return (
        <div>
            <div>BSon</div>
            {/* <button onClick={() => prop.handle()}>-</button> */}
            <>{prop.user.name}</>-<>{prop.user.age}</>
        </div>
    )
}
const ASonMemo = memo(ASon)
const BSonMemo = memo(BSon)
function About () {
    console.log('About渲染');
    const [num, setNum] = useState(5)
    const [aboutState, setAboutState] = useState(0)
    function increment() {
        setNum(num + 1)
    }
    const user = useMemo(() => {
        return {name: 'user1', age: 18}
    }, [])
    return (
        <div>
            <div>About</div>
            <div>{num}</div>
            <div>{aboutState}</div>
            <ASonMemo handle={increment}></ASonMemo>
            <BSonMemo user={user}></BSonMemo>
        </div>
    )
}
export default About

依赖内容为空,说明不会改变,其他组件(About、ASon)的重新渲染,不会对使用该数据的组件(BSon)造成影响

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

/*
1.什么是useMemo Hook?
useMemo用于优化代码, 可以让对应的函数只有在依赖发生变化时才返回新的值
* */
// 定义一个函数, 模拟耗时耗性能操作
function calculate() {
    console.log('calculate被执行了');
    let total = 0;
    for(let i = 0; i < 999; i++){
        total += i;
    }
    return total;
}
function App() {
    console.log('App被渲染了');
    const [numState, setNumState] = useState(0);
    // const total = calculate();
    const total = useMemo(()=>{
        return calculate();
    }, []);
    return (
        <div>
            <p>{total}</p>
            <p>{numState}</p>
            <button onClick={()=>{setNumState(numState + 1)}}>增加</button>
        </div>
    )
}
export default App;

useRef

  • useRef是createRef的Hook,比createRef更强大
  • useRef可以用来获取元素【和createRef一样不能直接获取函数式组件】、保存数据
  • useRef和useState的区别:useRef中保存的数据,除非手动修改,否则永远都不会发生变化

image.png

上面的报错:【获取元素】直接获取函数式组件

import {memo, createRef, useEffect, useRef, useState} from "react"
function ASon (prop) {
    return (
        <div>
            <div>ASon</div>
        </div>
    )
}
function BSon (prop) {
    return (
        <div>
            <div>BSon</div>
        </div>
    )
}
const ASonMemo = memo(ASon)
const BSonMemo = memo(BSon)
function About () {
    const ason = createRef()
    const bson = useRef() // 获取元素
    const data = useRef(0) // 保存数据
    const [numState, setNumState] = useState(0)
    const refData = useRef(numState)
    useEffect(() => {
        console.log('ason', ason.current);
        console.log('bson', bson.current);
        console.log('data', data.current)
    })
    useEffect(() => {
        console.log('改变了')
        refData.current = numState
    }, [numState]) // 依赖发生变化才会执行
    return (
        <div>
            <div ref={ason}>ason</div>
            <div ref={bson}>bson</div>
            {/* createRef不能获取函数组件 */}
            {/* <ASonMemo ref={ason}></ASonMemo> */}
            <div>numState: {numState}</div>
            <div>refData: {refData.current}</div>
            <button onClick={()=>setNumState(numState + 1)}>change</button>
            <ASonMemo></ASonMemo>
            <BSonMemo ></BSonMemo>
        </div>
    )
}
export default About

useImperativeHandle

  • 可以在使用ref时自定义暴露给父组件的实例值【权限控制】

不使用useImperativeHandle

可以直接操作暴露的元素的内容

import { useEffect, useRef, forwardRef} from "react"
function ASon (prop) {
    return (
        <div>
            <div>ASon</div>
        </div>
    )
}
function BSon (props, fason) {
    return (
        <div>
            <div>BSon</div>
            <input ref={fason} />
        </div>
    )
}
const ForwardASon = forwardRef(BSon)
function About () {
    const fason = useRef()
    useEffect(() => {
        console.log('fason', fason)
        fason.current.focus()
    })
    function setInput() {
        fason.current.value = '123'
    }
    return (
        <div>
            {/* <div ref={bson}>bson</div> */}
            {/* createRef不能获取函数组件 */}
            {/* <ASonMemo ref={ason}></ASonMemo> */}
            <ForwardASon ref={fason}></ForwardASon>
            <button onClick={() => setInput()}>input</button>
        </div>
    )
}
export default About

使用useImperativeHandle

可以对需要操作的内容进行控制

import { useEffect, useRef, forwardRef, useImperativeHandle} from "react"
function ASon (prop) {
    return (
        <div>
            <div>ASon</div>
        </div>
    )
}
function BSon (props, fason) {
    const inputRef = useRef()
    useImperativeHandle(fason, () => {
        return {
            myFocus: () => {
                inputRef.current.focus()
            },
            setInput: () => {
                inputRef.current.value = '123'
            }
        }
    })
    return (
        <div>
            <div>BSon</div>
            <input ref={inputRef} />
        </div>
    )
}
const ForwardASon = forwardRef(BSon)
function About () {
    const fason = useRef()
    useEffect(() => {
        console.log('fason', fason)
        fason.current.myFocus()
    })
    function setInput() {
        // fason.current.value = '123'
        fason.current.setInput()
    }
    return (
        <div>
            {/* <div ref={bson}>bson</div> */}
            {/* createRef不能获取函数组件 */}
            {/* <ASonMemo ref={ason}></ASonMemo> */}
            <ForwardASon ref={fason}></ForwardASon>
            <button onClick={() => setInput()}>input</button>
        </div>
    )
}
export default About

useLayoutEffect

需要修改DOM的布局样式,推荐使用useLayoutEffect

  • useEffect函数会在组件渲染到屏幕之前才执行,所以可能会出现闪屏
  • useLayoutEffect函数是在组件渲染到屏幕之前执行的,所以不会出现闪屏

为什么要使用useLayoutEffect来更新DOM布局和样式?

  • useEffect是组件已经渲染到屏幕上了才执行
  • useLayoutEffect是组件还没有渲染到屏幕上就会执行
  • 所以如果在组件已经渲染到屏幕上了, 才去更新DOM的布局和样式, 那么用户体验不好, 会看到闪屏的情况
  • 而如果是在组件还没有渲染到屏幕上, 就去更新DOM的布局和样式, 那么用户体验更好, 看不到闪屏情况

useEffect useLayoutEffect区别

  • 执行时间不同
    • 如果是挂载或更新组件,那么useLayoutEffect会比useEffect先执行
    • 如果是挂载组件,那么useEffect会比useLayoutEffect先执行

自定义hook

  • 通过自定义 Hook,可以对其它Hook的代码进行复用

注意点: 在React中只有两个地方可以使用Hook

  • 函数式组件中
  • 自定义Hook中

如何自定义一个Hooks

只要在函数名称前面加上use, 那么就表示这个函数是一个自定义Hook, 就表示可以在这个函数中使用其它的Hook

import React, {useEffect, useState} from 'react';
import './app.css'
/*
1.什么是自定义 Hook?
通过自定义 Hook,可以对其它Hook的代码进行复用
* */

/*
注意点: 在React中只有两个地方可以使用Hook
       - 函数式组件中
       - 自定义Hook中
如何自定义一个Hooks
只要在函数名称前面加上use, 那么就表示这个函数是一个自定义Hook, 就表示可以在这个函数中使用其它的Hook
* */
function useAddListenr(name) {
    useEffect(()=>{
        console.log(name, ' - 组件被挂载或者更新完成 -- 添加监听');
        return ()=>{
            console.log(name, ' - 组件即将被卸载 -- 移出监听');
        }
    });
}
function Home() {
    /*
    useEffect(()=>{
        console.log('Home - 组件被挂载或者更新完成 -- 添加监听');
        return ()=>{
            console.log('Home - 组件即将被卸载 -- 移出监听');
        }
    });
     */
    useAddListenr('Home');
    return (
        <div>Home</div>
    )
}
function About() {
    /*
    useEffect(()=>{
        console.log('About - 组件被挂载或者更新完成 -- 添加监听');
        return ()=>{
            console.log('About - 组件即将被卸载 -- 移出监听');
        }
    });
     */
    useAddListenr('About');
    return (
        <div>About</div>
    )
}
function App() {
    const [show, setShow] = useState(true);
    return (
        <div>
            {show && <Home/>}
            {show && <About/>}
            <button onClick={()=>{setShow(!show)}}>切换</button>
        </div>
    )
}
export default App;
import React, {createContext, useContext} from 'react';
/*
1.什么是自定义 Hook?
通过自定义 Hook,可以对其它Hook的代码进行复用
* */
const UserContext = createContext({});
const InfoContext = createContext({});
function useGetContext() {
    // 在企业开发中, 但凡需要抽取代码, 但凡被抽取的代码中用到了其它的Hook,
    // 那么就必须把这些代码抽取到自定义Hook中
    const user = useContext(UserContext);
    const info = useContext(InfoContext);
    return [user, info]
}
function Home() {
    // const user = useContext(UserContext);
    // const info = useContext(InfoContext);
    const [user, info] = useGetContext();
    return (
        <div>
            <p>{user.name}</p>
            <p>{user.age}</p>
            <p>{info.gender}</p>
            <hr/>
        </div>
    )
}
function About() {
    // const user = useContext(UserContext);
    // const info = useContext(InfoContext);
    const [user, info] = useGetContext();
    return (
        <div>
            <p>{user.name}</p>
            <p>{user.age}</p>
            <p>{info.gender}</p>
            <hr/>
        </div>
    )
}
function App() {
    return (
        <UserContext.Provider value={{name:'lnj', age:18}}>
            <InfoContext.Provider value={{gender:'man'}}>
                <Home/>
                <About/>
            </InfoContext.Provider>
        </UserContext.Provider>
    )
}
export default App;


学习笔记