介绍:

官网上,给Hook的定义是:一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

本篇文章主要分为三大部分:

  1. 内置Hooks
  2. 自定义Hooks
  3. Hook的使用规则

内置的几个Hook

useState

入参:定义的state中变量的初始值
返回值:定义的state中变量名; 修改此变量的方法, 更新此变量时,总是替换它,而不是合并它(和this.setState( ) 区别的合并不同,注意区别)
例1:

// 声明一个count的state变量,它的初始值是0,并且通过setCount方法更新count
const [count, setCount] = useState(0);

例2:

// 可在一个react组件中定义多个useState
const MultiThing = () => {
    const [age, setAge] = useState(30)
  const [] = useState('apple')
  // error变量是Error或者null类型,并且初始值是null
  const [error, setError] = useState<Error | null>(null);
}

接下来,会继续介绍其他Hook和项目中用到的自定义Hook

useEffect

官方说明:
1,可以在函数组件中执行副作用操作
2,每次运行 effect 的同时,DOM 都已经更新完毕
使用场景:useEffect会在第一次渲染之后和每次更新之后都会执行。
理解:useEffect可看成React三个生命周期钩子函数componentDidMount 、 componentDidUpdate和 componentWillUNmount 的组合
特点:
1,函数里可以访问组件的 props 和 state中的变量
2,可以在组件中多次使用useEffect
副作用操作分为需要清除和不需要清除两种:
1,对于不需要清除的,useEffect函数参数无返回值:

  useEffect(() => {
    document.title = title;
    // 没有写返回值
  }, [title]);

对于需要清除的,在useEffect函数体中,返回值为清除的函数:
执行的过程,看官网的这张图, 很好的解释了执行顺序的问题:

2,

useEffect(() => {
    let timeOut = setTimeout(() => {
      setDebouncesValue(param);
    }, time);
    // useEffect 会在组件卸载之前执行清除函数。即先执行清除函数,再执行useEffect函数体的内容。本例,先清掉上一次设的定时器timeOut,再重新设个定时器
    return () => clearTimeout(timeOut);
  }, [param, time]);

跳过Effect进行性能优化
1,useEffect第二个参数传入数组,作为限定。如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 effect。

  useEffect(() => {
    return () => {
      if (!keepOnUnmount) {
        document.title = oldTitle;
      }
    };
  }, [keepOnUnmount, oldTitle]);

2,如果想某个effect仅在挂载组件时,执行一次,第二个组件,可传入空数组[]

export const useMount = (callback: () => void) => {
  useEffect(() => {
    callback();
    // 依赖项里加callback,会造成无限循环,这和useCallback和useMemo有关
    // eslint的报错信息不一定对
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
};

3, 官方文档里,useEffect函数体里用到的变量,在第二个数组参数里面要体现,但是有时候,加上了,会造成无限循环,此时,需要关掉eslint的报错信息

useReducer

官网上写是对useState的替代方案。
语法: const [state, dispatch] = useReducer(reducer, initialState, init)
init 是将initialState存入state的方法,reducer是处理state变量的过程
用法:组件中,通过调用dispatch方法,传入reducer中需要额外传入的参数; 通过state变量,访问存入的属性
官网的例子: https://codesandbox.io/s/agit...
可以看看下面例子:

// 初始化变量 initialState
const initialState = { count: 0 }

// 初始化变量修改方法recuder
const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 }
        case 'decrement':
            return { count: state.count - 1 }
        default:
            return state
    }
}

function App() {
    // 初始化 useReducer。 很像 const [count, setCount] = useState(0), 赋初始值时,多传了个reducer
    const [state, dispatch] = useReducer(reducer, initialState)

    return <div>
        count: {state.count}
        {/* 修改变量,用dispatch, 传type的方式 */}
        <button onClick={() => dispatch({ type: 'increment' })}>increment</button>
        <button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
    </div>
}


export default App

useCallback和useMemo

用法总结:

  • useMemo 缓存数据
  • useCallback 缓存函数
  • 两者是React Hooks的常见优化策略

useMemo用法:

  1. 先用memo包住子组件
  2. 将需要传给子组件的对象,外面包一层useMemo

    // 子组件, memo 相当于 PureComponent,对 props 进行浅层比较
    const Child = memo(({ userInfo }) => {
     console.log('Child render...', userInfo)
    
     return <div>
         <p>This is Child {userInfo.name} {userInfo.age}</p>
     </div>
    })
    
    // 父组件
    function App() {
     const [count, setCount] = useState(0)
     const [name, setName] = useState('双越老师')
    
     // 用 useMemo 缓存数据,有依赖
     const userInfo = useMemo(() => {
         return { name, age: 21 }
     }, [name])
    
     return <div>
         <p>
             count is {count}
             <button onClick={() => setCount(count + 1)}>click</button>
         </p>
         <Child userInfo={userInfo}></Child>
     </div>
    }

useCallback用法:

  1. 先用memo包住子组件
  2. 将需要传给子组件的函数,外面包一层useCallback
    改造以下上面的父组件,加一个onChange事件

     // 用 useCallback 缓存函数
     const onChange = useCallback(e => {
         console.log(e.target.value)
     }, [])
    
     // 父组件内部
     <Child userInfo={userInfo} onChange={onChange}></Child>

useRef

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

  1. ref对象的current属性的初始值是useRef(param) 传入的参数param: const oldTitle = useRef(document.title).current;
  2. useRef创建的是一个JavaScript对象,而且useRef会在每次渲染时,都返回同一个ref对象
  3. 变更ref.current,不会引发组件重新渲染

两个应用场景:

  1. 获取DOM节点
  2. 获取某个ref属性的值
    看例子:

    const btnRef = useRef(null) // 初始值
    
    // const numRef = useRef(0)
    // numRef.current // 获取 numRef
    
    useEffect(() => {
     console.log(btnRef.current)  // 获取DOM节点
    })
    
    return <div ref={btnRef}>click</div>

useContext

作用:跨组件中,值的传递
逻辑图:

用法:

  • React.createContext 创建 一个Context, ThemeContext
  • 在出口函数里使用 ThemeContext.provider 标签,value传入想传的值
  • 在终端需要用到theme的地方,调用useContext,取到theme的值
    以上步骤用代码实现:

    // 主题颜色
    const themes = {
      light: {
          foreground: '#000',
          background: '#eee'
      },
      dark: {
          foreground: '#fff',
          background: '#222'
      }
    }
    
    
    // 创建 Context
    const ThemeContext = React.createContext(themes.light) // 初始值
    
    
    function ThemeButton() {
      const theme = useContext(ThemeContext)
    
    
      return <button style={{ background: theme.background, color: theme.foreground }}>
          hello world
      </button>
    }
    
    
    function Toolbar() {
      return <div>
          <ThemeButton></ThemeButton>
      </div>
    }
    
    
    function App() {
      return <ThemeContext.Provider value={themes.dark}>
          <Toolbar></Toolbar>
      </ThemeContext.Provider>
    }
    
    
    export default App 

useImperativeHandle

自己写的例子:https://codesandbox.io/s/agit...
官网:描述内容少,写了两点。

  1. 在使用ref时,自定义暴露给父组件的实例值(这里的实例值的叫法,有点怪,其实是,可以调用的方法)
  2. useImperativeHandle 和forwardRef要一起使用

理解的过程:
useImperativeHandle中间的单词imperative是 “必要的”的意思。结合用法,这个hook的意思是:如果需要父组件调用子组件抛出的方法,进行必要的处理。
子组件用useImperativeHandle,传入自身的ref和抛出的方法。比如 实例中的setVal 和setFus,就是抛出的方法。
父组件通过绑定在子组件上的ref,调用抛出的方法。比如实例中的 setFancyFocus 和 setFancyVal 函数体

useLayoutEffect

官网:描述内容少。
说明:

  1. 函数签名 与 useEffect相同,不同之处是,它会在所有的 DOM 变更之后同步调用 effect
  2. 可以使用它来读取 DOM 布局并同步触发重渲染

后面用到了,再补充其使用场景。

useDebugValue

用法:useDebugValue(value) 在开发者工具中显示自定义hook的标签
要点:

  1. 用在自定义hook内部
  2. 格式化值的显示可能是一项开销很大的操作,除非需要检查 Hook,否则没有必要这么做
  3. useDebugValue可以接受格式化函数,作为第二个参数。后面用到了,再补充。

其他hook,后面会继续补充。

自定义Hook:

如果函数的名字以 “use” 开头并调用其他 Hook,我们就说这是一个自定义 Hook
使用场景:组件之间重用一些状态逻辑。
特点:自定义Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。

// 自定义Hook useAxios
function useAxios(url) {
    const [loading, setLoading] = useState(false)
    const [data, setData] = useState()
    const [error, setError] = useState()

    useEffect(() => {
        setLoading(true)
        axios.get(url)
        .then(res => setDate(res))
        .catch(err => setError(err))
        .finally(() => setLoading(false))
    }, [url])
    
    return [loading, data, error]
}

Hook的使用规则

  • Hooks的命名规范,useXXX
  • Hooks使用规范

    • 只能用在React函数组件和自定义Hook中,其他地方不可以(其他地方,比如class组件)
    • 只能用于顶层代码,不能在循环、判断 中使用Hooks,不可在Hook之前 return
    // Hooks错误使用的例子
    import React, { useState, useEffect } from 'react'
    
    function Teach({ couseName }) {
        const [studentName, setStudentName] = useState('张三')
        // 错误,Hooks不能放在 if判断语句中
        if(couseName === ''){
            useEffect(() => {
                localStorage.setItem('name', studentName)
            })
        }
        // 错误,Hooks不能放在for循环中
        for(let i = 0; i < 100; i ++){
            const [teacherName, setTeacherName] = useState('双越')
        }
        if(!couseName) {
            return
        }
            // 错误,Hooks不能在可能被打断的逻辑后面
        useEffect(() => {
            console.log(`${teacherName} 开始讲课,学生是 ${studentName}`)
        })
        return <div>课程:{couseName} ,讲师: {teacherName}, 学生:{studentName}</div>
    }
    
    export default Teach
    • eslint 插件 eslint-plugin-react-hooks 有利于Hooks使用
  • 关于Hooks的调用顺序

    • 无论是render还是re-render,Hooks调用顺序必须保持一致

      • 对 useState 来说,render时,初始化state的值;re-render时,读取state变量的值
      • 对useEffect来说,render时,初始化effect函数;re-render时,替换effect函数
    • 如果Hooks出现在循环、判断、则无法保证顺序一致

      • 不管是官方Hook还是自定义Hook 之所以能保证state变量和后面的值一一对应,是按照严格的顺序执行的方式,来保证的
    • Hooks严重依赖于调用顺序

joychenke
47 声望3 粉丝

业精于勤荒于嬉。加油ヾ(◍°∇°◍)ノ゙