react hook

396 阅读4分钟

Hook 简介

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

React 中内置的 Hook API

引入

import  { useState, useEffect, useReducer, useCallback, useLayoutEffect, useDebugValue, useRef, useContext} from 'react';

基础 API

  • useState
  • useEffect
  • useContext

1.useState

 function Example() {
    const [count, setCount] = useState(0);
   return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>
      增加
       </button>
    </div>
    ) }
    

2. useEffect

  • 基础

    function Example() {
       useEffect(() => {
       const interval = setInterval(() => {
     }, 1000)
       })
     
  • 清除 在 React class 中,你通常会在 componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它

 function Example() {
useEffect(() => {
  const interval = setInterval(() => { 
  }, 1000)
  return () => {
    clearInterval(interval)
    
  }
})

return (
  <>
    Example组件
  </>
);
  }

effect 的条件执行:

  1. 默认情况下,effect 会在每轮组件渲染完成后执行
  2. 可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组
   function Example(props) {
   useEffect(() => {},[props.count])
    return <>Example组件</>
     }

useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect

3. useContext


       const TestContext= React.createContext({username: ''})

       const Navbar = () => {
        const {username} = useContext(TestContext)
        return (
        <div className="navbar">
        <p>{username}</p>
        </div>
        )
      }

     const Messages = () => {
      const {username} = useContext(TestContext)
      return (
      <div className="messages">
        <p>1 message for {username}</p>
      </div>
    )
      }

      function Example() {
     return (
      <TestContext.Provider
        value={{
          username: 'superawesome',
       }}
    >
        <div className="test">
     <Navbar />
         <Messages />
        </div>
       </TestContext.Provider>
      )


       }
 

额外的 Hook

  • useReducer
  • useRef
  • useCallback
  • useMemo
  • useLayoutEffect

1. useReducer

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

             const initialState = {count: 0};

           function reducer(state, action) {
              switch (action.type) {
              case 'increment':
             return {count: state.count + 1};
              case 'decrement':
             return {count: state.count - 1};
              default:
              throw new Error()  }

   function Example() {
    const [state, dispatch] = useReducer(reducer, initialState);
     return (
      <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
     <button onClick={() => dispatch({type: 'increment'})}>+</button>
      </>
     )
      }

惰性初始化

  const initialState = {count: 0};
    function init(initialCount){
      return {count:initialCount+1}
    }

    function reducer(state, action) {
      switch (action.type) {
      case 'increment':
      return {count: state.count + 1};
      case 'decrement':
      return {count: state.count - 1};
     default:
     throw new Error();
  }
    }

     function Example() {
   const [state, dispatch] = useReducer(reducer, initialCount);
    return (
     <>
     Count: {state.count}
     <button onClick={() => dispatch({type: 'decrement'})}>-</button>
     <button onClick={() => dispatch({type: 'increment'})}>+</button>
     </>
   );
    }


2. useRef

它像一个变量, 类似于 this , 它就像一个盒子, 你可以存放任何东西. createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用

function Example() {
 const inputEl:React.MutableRefObject<any> = useRef(null);
 const onButtonClick = () => {
   // `current` 指向已挂载到 DOM 上的文本输入元素
   inputEl.current.focus();
 };
 return (
   <>
     <input ref={inputEl} type="text" />
     <button onClick={onButtonClick}>Focus the input</button>
   </>
 );
}

3. useCallback

    let count = 0;

   function App() {
    const [val, setVal] = useState('');

      function getData() {
     setTimeout(() => {
      setVal('new data ' + count);
       count++;
     }, 500);
   }

     return <Child val={val} getData={getData} />;
    }

     function Child({val, getData}:any) {
      useEffect(() => {
        getData();
     }, [getData]);

     return <div>{val}</div>;
       }


     function App() {
const [val, setVal] = useState('');

const getData = useCallback(() => {
 setTimeout(() => {
   setVal('new data ' + count);
   count++;
 }, 500);
 }, []);

return <Child val={val} getData={getData} />;
   }

   function Child({val, getData}:any) {
   useEffect(() => {
    console.log('%c 🍩 useEffect: ', 'font-size:20px;background-color: #2EAFB0;color:#fff;', 'useEffect');
   getData();
  }, [getData]);

 return <div>{val}</div>;
  }



4. useMemo

  • useMemo和useCallback的区别 及使用场景
  • useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据
共同作用:
  1. 仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别:
  1. useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态
  2. useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该 缓存起来,提高性能,和减少资源浪费

5. useLayoutEffect

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

  • 使用 useEffect

   function Example() {
       const [count, setCount] = useState(0);

      useEffect(() => {
        console.log(`useEffect - count=${count}`)

        const pre = Date.now();
        while (Date.now() - pre < 500) {}

        if (count === 0) {
        setCount(10 + Math.random() * 200);
      }
       }, [count]);

       return (
      <div onClick={() => setCount(0)}>{count}</div>
     );
      }


  • 使用 useLayoutEffect
    function Example() {
      const [count, setCount] = useState(0);
       useLayoutEffect(() => {
        console.log(`useLayoutEffect - count=${count}`)
       const pre = Date.now();
       while (Date.now() - pre < 500) {}
       if (count === 0) {
         setCount(10 + Math.random() * 200);
      }
     }, [count]);

    return (
      <div onClick={() => setCount(0)}>{count}</div>
   );
   }

  • useLayoutEffect和componentDidMount、componentDidUpdate触发时机一致
  • useEffect在浏览器渲染完成后执行
  • useLayoutEffect在浏览器渲染前执行
  • useLayoutEffect总是比useEffect先执行
  • useLayoutEffect里面的任务最好影响了Layout(布局)

自定义 Hook

const useWinSize = () => {
 // 1. 使用useState初始化窗口大小state
 const [size, setSize] = useState({
   width: document.documentElement.clientWidth,
   height: document.documentElement.clientHeight
 });

 const changeSize = useCallback(() => {
   // useCallback 将函数缓存起来 节流
   setSize({
     width: document.documentElement.clientWidth,
     height: document.documentElement.clientHeight
   });
 }, []);
 // 2. 使用useEffect在组件创建时监听resize事件,resize时重新设置state (使用useCallback节流)
 useEffect(() => {
   // 绑定一次页面监听事件 组件销毁时解绑
   window.addEventListener('resize', changeSize);
   return () => {
     window.removeEventListener('resize', changeSize);
   };
 }, []);

 return size;
};


Hook 规则

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确

//错误
 function Example() {
 if (1<2) {
   const [count, setCount] = useState(0);
 }

 return (
   <div>Example </div>
 )
}

只在 React 函数中调用 Hook

  • 不要在普通的 JavaScript 函数中调用 Hook。你可以
    • 在 React 的函数组件中调用 Hook
    • 在自定义 Hook 中调用其他 Hook