React Hook学习笔记

338 阅读6分钟

React Hook

认识Hook

Hook是什么?Hook是一个特殊的函数,它可以让你在函数组件里“钩入”React的特性,在不编写class的情况下使用state以及其他的React特性。

优点 Hook可以让你在不需要修改组件结构的情况下复用状态逻辑;Hook可以将组件中相互关联的部分拆分为更小的函数(比如设置订阅或请求数据);Hook使你在非class的情况下可以使用更多的React特性。

Hook规则

  • Hook不能在class组件中使用
  • 只能在最顶层使用Hook,不要在循环、条件或嵌套函数中调用Hook:因为React是靠Hook的调用顺序来将 保证 stateuseState 一一映射的。
  • 只在React函数中调用Hook或者在自定义Hook中调用其他Hook,不要在普通的JavaScript函数中调用Hook

Hook API

  • useState可以使用多个state变量

    与 class 组件中的 setState方法不通,useState不会自动合并更新对象。可以使用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

    setState(prevState => {
      // 也可以使用 Object.assign
      return {...prevState, ...updatedValues};
    });
    
    import React, { useState } from 'react'
    
    function Demo() {
        const [count, setCount] = useState(0)
        
        return (
       	 <div>
           	<p>{count}</p>
              <button onClick={() => setCount(count + 1)}>click</button>
           </div> 
        )
    }
    
    • **调用 useState方法时做了什么?**它定义了一个“state变量”。

    • useState需要哪些参数?useState()方法里唯一的参数就是初始state。可以是数字、字符串或者对象。

    • **useState方法的返回值是什么?**返回值为:当前state以及更新state的函数。

  • useEffect:可以在函数组件中执行副作用操作。

    useEffect Hook可以看做componentDidMount, componentDidUpdatecomponentWillUnmount这三个函数的组合。

    import React, { useState, useEffect } from 'react'
    
    function Demo() {
        const [count, setCount] = useState(0)
        
        useEffect(() => {
            document.title = `{count} 次`
        })
        
        return (
       		<div>
           		<p>{count}</p>
                <button onClick={() => setCount(count + 1)}>click</button>
            </div> 
        )
    }
    
    • **useEffect做了什么?**React会保存传递的函数,并且在执行DOM更新之后调用它。

    • **为什么在组件内部调用useEffect?**将useEffect放在组件内部可以让我们在effect中直接访问state变量。Hook使用了闭包机制。

    • **useEffect会在每次渲染后都执行吗?**默认情况下,在第一次渲染之后和每次更新之后都会执行。

    需要清除的effect

    通常,组件卸载时需要清除 effect 创建时的诸如订阅或者计时器ID等资源。要实现这一点,useEffect函数需要返回一个清除函数,以防止内存泄漏,清除函数会在组件卸载前执行

    import React, { useState, useEffect } from 'react'
    
    function Demo() {
        const [date, setDate] = useState(new Date())
        
        useEffect(() => {
            const timer = setInterval(() => {
                setDate(new Date())
    		}, 1000)
            
            return () => clearInterval(timer)
        })
        
        return (
       		<div>
           		<p>{date.toLocaleTimeString()}</p>
            </div> 
        )
    }
    

effect 的条件执行

​ 默认情况下,effect 将在每轮渲染结束后执行,但可以选择让它在只有某些值改变的时候才执行。

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

function Demo() {
	const [count, setCount] = useState(0)
    const [date, setDate] = useState(new Date())
    useEffect(() => {
    	document.title = `{count} 次`
    }, [count])
    
    useEffect(() => {
        const timer = setInterval(() => {
            setDate(new Date())
		}, 1000)
        
        return () => clearInterval(timer)
    })
    
    return (
   		<div>
   			<p>{count}</p>
            <button onClick={() => setCount(count + 1)}>click</button>
       		<p>{date.toLocaleTimeString()}</p>
        </div> 
    )
}

​ 此时,只有当 useEffect 第二个参数数组里的数值改变后才会重新创建订阅。

如果要使用此优化方式,要确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量,否则代码会引用到先前渲染中的旧变量。

如果像执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。

如果传入了一个空数组([]), effect 内部的 props 和 state 就会一直持有其初始值。

  • useContext

    接收一个 context 对象(React.createContext的返回值)并返回改 context 的当前值。当前的context 值由上层组件中距离当前组件最近 <MyContext.Provider>value prop决定。

    useContext 的参数必须是 context 对象本身。例:useContex(MyContext)

    调用了useContext的组件总会在 context 值变化时重新渲染。

    const themes = {
    	light: {
            color: '#000',
            background: '#eee'
        },
        dark: {
            color: 'blue',
            background: '#222'
        }
    }
    
    const ThemeContext = React.createContext(themes.light)
    
    function App() {
        return (
       		<ThemeContext.Provider value={themes.dark}>
           		<Toolbar /> 
            </ThemeContext.Provider> 
        )
    }
    
    function Toolbar(props) {
        return (
            <div>
            	<ThemeButton />
            </div>
        )
    }
    
    function ThemeButton() {
        const theme = useContext(ThemeContext)
        return (
       		<button style={{background: theme.background, color: theme.color}}>theme</button> 
        )
    }
    
  • useReducer

    const [state, dispatch] = useReducer(reducer, initialArg, init)
    

    接受一个形如(state, action) => newState的 reducer ,并返回当前的state以及与其配套的dispatch方法。

    例:

    const initialState = {
        count: 0
    }
    
    function reducer(state, action) {
        switch (action.type) {
            case 'increment':
                return {count: state.coount + 1}
            case 'decrement':
                return {count: state.coount - 1}
            default:
                throw new Error()
        }
    }
    
    function Counter() {
        const [state, dispatch] = useReducer(reducer, intialState)
        return (
       		<>
            	count: {state.count}
    			<button onClick={() => dispatch({type: 'decrement'})}>-</button>
    			<button onClick={() => dispatch({type: 'increment'})}>+</button>
            </>
        )
    }
    
  • useCallback

    把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized版本,该回调函数尽在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

    const memoizedCallback = useCallback(() => {
    	doSomething(a, b)
    }, [a, b])
    

    useCallback(fn, deps)相当于 useMemo(() => fn, deps)

    例:

    import React, { useState, useCallback, PureComponent } from 'react'
    
    export default function UseCallbackPage(props) {
        const [count, setCount] = useState(0)
        const addClick = useCallback(() => {
            let sum = 0
            for (let i = 0; i < count; i++) {
                sum += i
            }
            return sum
        }, [count])
        
        return (
       		<div>
           		<p>{count}</p>
                <button onClick={() => setCount(count + 1)}>add</button>
                <Child addClick={addClick}/>
            </div> 
        )
    }
    
    class Child extends PureComponent {
    	render() {
            const { addClick } = this.props
            return (
           		 <button onClick={() => console.log(addClick())}>add</button>
            )
        }
    }
    
  • useMemo

    把“创建”函数和依赖数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算memoized值。这种优化有助于避免在每次渲染时都进行高开销的计算。

    import React, { useState, useMemo } from 'react';
    
    export default function UseMemoPage(props) {
        const [count, setCount] = useState(0)
        const expensive = useMemo(() => {
            let sum = 0
            for (let i = 0; i < count; i++) {
                sum += i
            }
            return sum
        }, [count])
        
        return (
       		<div>
           		<p>{count}</p>
                <p>{expensive}</p>
                <button onClick={() => setCount(count + 1)}>add</button>
            </div> 
        )
    }
    
  • useRef

    const refContainer = useRef(value)
    

    useRef返回一个可变的 ref 对象,其 .current属性被初始化为传入的参数。返回的 ref 对象在组件的整个生命周期内保持不变。

    import React, { useRef } from 'react';
    
    function Demo() {
        const inputEl = useRef(null)
        const handleClick = () => {
            inputEl.current.focus()
        }
        
        return (
       		<input ref={inputEl} type="text"/> 
        )
    }
    
  • useImperativeHandle

    useImperativeHandle可以让你在使用ref时自定义暴露给父组件的实例值。

    import React, { useRef, useImperativeHandle, forwardRef } from 'react';
    
    function FancyInput(props, ref) {
        const inputRef = useRef()
        useImperativeHandle(ref, () => ({
            focus: () => {
                inputRef.current.focus()
            }
        }))
        
        return <input ref={inputRef} .../>
    }
    FancyInput = forwardRef(FancyInput)
    
  • useLayoutEffect

    其函数签名与useEffect相同,但他会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前, useLayoutEffect内部的更新计划将被同步刷新。

  • useDebugValue

    useDebugValue可用于在 React 开发者工具中显示自定义 Hook 的标签。

自定义Hook

有时候我们需要在组件之间重用一些状态逻辑。目前为止,有两种主流方案来解决这个问题:

。自定义Hook可以让你在不增加组件的情况下达到同样的目的。

自定义Hook是一个函数,其名称以“use”开头,函数内部可以调用其他的Hook。

// useClock.js
import {useState, useEffect} from 'react'
function useClock() {
    const [date, setDate] = useState(new Date())
    
    useEffect(() => {
        const timer = setInterval(() => {
            setDate(new Date())
        }, 1000)
        
        return () => clearInterval(timer)
    }, [])
    
    return date
}

// 使用
function Demo() {
    const date = useClock()
    
    return (
   		<p>{date}</p> 
    )
}