Skip to content

React之Hooks知识点

知识点

重要就6个Hooks:

  • useState
  • useEffect
  • useContext
  • useRef
  • useCallback
  • useMemo

useState

React 以前都是以类来实现,新版本实现是以函数来实现。React 现在重点以函数为主,函数是 React 的第一公民

  • 利用函数不用在改变 this 指针

useState介绍

  • 它的主要作用就是保存状态,他接受一个参数就是默认值,它返回一个数组,数组第一个就是数据,第二个就是改变数据的方法

  • 改变数据的方法里面的参数是函数,函数必须有 return

javascript
import React, { useState } from "react";
const APP = () => {
  const [name, setname] = useState("首页");
  function changename() {
    setname(() => {
      let name = "首页";
      return name + parseInt(10 * Math.random());
    });
  }
  return (
    <div>
      <span>{name}</span>
      <button onClick={changename}>点击修改</button>
    </div>
  );
};

export default APP;

useState 注意的四个方面

组件会重新执行 避免死循环

javascript
import { useState } from "react";

function FunctionPage() {
  const [count, setCount] = useState(0);
  setCount(count + 1);
  return (
    <div>
      <h1>FunctionPage</h1>
    </div>
  );
}

export default FunctionPage;

注意

你这样做 代码会无限循环,因为只要数据更新,它就会重新执行,重新执行的时候又会执行 setCount(count + 1);所以会无限循环

异步执行

  • 错误写法
javascript
import { useState } from "react";

function FunctionPage() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    console.log("点击测试结果", count);
  };
  return (
    <div>
      <div>结果是{count}</div>
      <button onClick={handleClick}>点击我</button>
    </div>
  );
}

export default FunctionPage;

注意

你这样写的时候,你会发现 count 只会加 1,因为 setCount 是异步的,它会先执行完所有的 setCount,然后再执行 console.log("点击测试结果", count);所以 count 只会加 1

  • 正确写法
javascript
import { useState } from "react";

function FunctionPage() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount((count) => count + 1);
    setCount((count) => count + 1);
    setCount((count) => count + 1);
    setCount((count) => count + 1);
    setCount((count) => {
      console.log("结果是" + count);
      return count;
    });
  };
  return (
    <div>
      <div>结果是{count}</div>
      <button onClick={handleClick}>点击我</button>
    </div>
  );
}

export default FunctionPage;

注意

在开发模式和严格模式下你会发现 console 会执行了两次,这是因为 React 会在每次渲染后执行 useEffect,所以会执行两次,但是生产模式下就不会执行两次,要是想去掉直接在 main.jsx 里面去掉<StrictMode>就可以了

缓存

如果在短时间内大量多次频繁的渲染数据,它渲染数据仅仅会执行一次,因为它是缓存了

javascript
import { useState } from "react";

function FunctionPage() {
  const [count, setCount] = useState(0);
  console.log("组件渲染");
  const handleClick = () => {
    setCount((count) => count + 1);
    setCount((count) => count + 1);
    setCount((count) => count + 1);
    setCount((count) => count + 1);
    setCount((count) => {
      console.log("结果是" + count);
      return count;
    });
  };
  return (
    <div>
      <div>结果是{count}</div>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        点击我
      </button>
    </div>
  );
}

export default FunctionPage;

注意

  1. 你会发现 console.log("组件渲染")只执行了一次,因为它是缓存了
  • 第二种不适用 useState 的话 它没有缓存了
javascript
import { useState } from "react";

function FunctionPage() {
  const [count, setCount] = useState(0);
  let x = 100;
  const handleClick = () => {
    setCount((count) => {
      count += 1;
      x += 1;
      console.log(`count:${count}那么x:${x}`);
      return count;
    });
  };
  return (
    <div>
      <div>结果是{count}</div>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        点击我
      </button>
    </div>
  );
}

export default FunctionPage;

警告

在这里面你会发现 x 永远是 101

函数内部使用

注意

你要是把 useState 现在函数外面,是不可以的. 它会直接报错

useEffect

useEffect 主要的作用就是替代生命周期函数

  • 它接收两个参数,第一个参数表示回调函数,第二个表示生命周期函数

  • 第二个参数表示它什么时候执行,如果没有第二个参数表示只要组件渲染都执行

重点第二个函数

  • 当 useEffect 没有第二个参数

组件的初始化和更新都会执行

javascript
useEffect(() => {
  console.log("哈哈哈");
});
  • 当 useEffect 第二个参数是空数组(仅仅执行一次)

初始化,和卸载的时候执行,相当于 componentDidMount,componentWillUnMount

javascript
useEffect(() => {
  console.log("哈哈哈");
}, []);
  • 当有一个或者多个值的数组
javascript
useEffect(() => {
  console.log("哈哈哈");
}, [name, age]);

当有一个值的时候,该值有变化就执行

当有多个值的时候,只要有一个不相等就执行

监听函数

  • 有 return 就表示卸载
javascript
useEffect(() => {
  //监听
  window.addEventListener("resize", onResize, false);
  //监听完了就考虑卸载,卸载的时候必须是return
  return () => {
    window.removeEventListener("resize", onResize, false);
  };
}, []);

useContext

Context 主要是为了方便爷孙之间的传值,有的时候不希望一层层传递了,需要跨级传递

(1)创建并暴露出 Context

javascript
import React, { useState, createContext } from "react";
export const ResultContext = createContext();

(2) 父组件

  • 里面必须包含子组件

  • 并且用 provider 包裹

javascript
import React, { useState, createContext } from "react";
import HeaderComponent from "./component/Header";
export const ResultContext = createContext();
const APP = () => {
  const [age, setage] = useState(0);
  function handleClick() {
    setage(() => {
      return age + 1;
    });
  }
  return (
    <div>
      <div>{age}</div>
      <button onClick={handleClick}>点击改变</button>
      <ResultContext.Provider value={age}>
        <HeaderComponent></HeaderComponent>
      </ResultContext.Provider>
    </div>
  );
};

export default APP;

(3) 子组件使用

  • 函数 必须使用 useContext
javascript
import React, { useContext } from "react";
import { ResultContext } from "../App";
const HeaderComponent = () => {
  const data = useContext(ResultContext);
  return <div>子属性{data}</div>;
};

export default HeaderComponent;
  • 要是类 就必须用 Provider 和 Consumer
javascript
import React, { Component, Fragment } from "react";
import { PublicDataOne, PublicDataTwo } from "./App";
class ThirdWrapper extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: "第三层使用公共数据",
    };
  }
  render() {
    return (
      <Fragment>
        <div>
          {this.state.message}
          <PublicDataOne.Consumer>
            {(value) => {
              return (
                <PublicDataTwo.Consumer>
                  {(valueTwo) => {
                    return (
                      <h1>
                        这就是第一个公共数据{value} 这就是第二个公共数据
                        {valueTwo}
                      </h1>
                    );
                  }}
                </PublicDataTwo.Consumer>
              );
            }}
          </PublicDataOne.Consumer>
        </div>
      </Fragment>
    );
  }
}

export default ThirdWrapper;

useref

  • 记忆功能(与 useState 区别)

  • 获取 dom

  • 组件 ref

  • 暴露出组件方法

警告

  • useRef 不触发渲染

  • useRef 必须得用 current 来获取值

记忆功能

  • 不触发渲染

举例子

  • 这种情况下 它清除不了最开始的 timer,而是自己又新开了一个
js
import React, { useRef } from "react";
import Son from "./Son";
function App() {
  let timer = null;
  const handleClick = () => {
    clearInterval(timer);
    timer = setInterval(() => {
      console.log("hello");
    }, 1000);
  };
  return (
    <div>
      <h1 onClick={handleClick}>定时器</h1>
      <Son></Son>
    </div>
  );
}

export default App;

解决这个问题

  • 使用 useRef 来解决
js
import React, { useRef } from "react";
import Son from "./Son";
function App() {
  let timer = useRef(null);
  const handleClick = () => {
    clearInterval(timer.current);
    timer.current = setInterval(() => {
      console.log("hello");
    }, 1000);
  };
  return (
    <div>
      <h1 onClick={handleClick}>定时器</h1>
      <Son></Son>
    </div>
  );
}

export default App;

获取 dom

  • 获取 dom 改变文字颜色
js
import React, { useRef } from "react";
import Son from "./Son";
function App() {
  const h1dom = useRef(null);
  const handleClick = () => {
    h1dom.current.style.color = "red";
  };
  return (
    <div>
      <h1 onClick={handleClick} ref={h1dom}>
        定时器
      </h1>
      <Son></Son>
    </div>
  );
}

export default App;

组件 ref

  • 给组件绑定 ref

  • forwardRef 包裹组件

  • 通过 ref 指定元素

父元素

js
import React, { useRef } from "react";
import Son from "./Son";
function App() {
  const h1dom = useRef(null);
  const handleClick = () => {
    console.log(h1dom.current); // 就能获取到 <h1>son</h1
  };
  return (
    <div>
      <h1 onClick={handleClick}>获取dom</h1>
      <Son ref={h1dom}></Son>
    </div>
  );
}

export default App;

子元素

    1. Son 括号里面不用{} 直接就是 props,ref
    1. forwardRef 包裹组件
    1. 通过 ref 指定元素
js
import React, { forwardRef } from "react";
function Son(props, ref) {
  console.log("子元素渲染");
  return (
    <div>
      <h1 ref={ref}> Son</h1>
    </div>
  );
}

export default forwardRef(Son);

暴露出组件的方法

  • Son 还是得括号里写 props,ref

  • useImperativeHandle 指定 ref 暴露的方法或者属性,必须 return

通过 ref 暴露出我想指定的方法

子元素

js
import React, { forwardRef, useImperativeHandle } from "react";
function Son(props, ref) {
  // 指定暴露的方法
  useImperativeHandle(ref, () => {
    return {
      fn1() {
        console.log("子组件暴露的方法1");
      },
      fn2() {
        console.log("子组件暴露的方法2");
      },
    };
  });
  return (
    <div>
      <h1> Son</h1>
    </div>
  );
}

export default forwardRef(Son);

父元素

js
import React, { useRef } from "react";
import Son from "./Son";
function App() {
  const h1dom = useRef(null);
  const handleClick = () => {
    console.log(h1dom.current);
    h1dom.current.fn1();
    h1dom.current.fn2();
  };
  return (
    <div>
      <h1 onClick={handleClick}>获取dom</h1>
      <Son ref={h1dom}></Son>
    </div>
  );
}

export default App;

缓存

  • memo
  • usememo
  • useCallback

memo

主要就是应用于 HTML

memo 是一个高阶组件,用于对组件进行包裹,从而对组件进行优化,减少组件的渲染次数

适用场景

当父元素变化 而子元素不需要变化的时候,用它包裹住组件,减少渲染

父元素

js
import { useCallback, useReducer, useState } from "react";
import Son from "./Son";
function App() {
  const [count, setCount] = useState(0);
  console.log("父元素渲染");
  const handleClick = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <h1 onClick={handleClick}>父元素--{count}</h1>
      <Son />
    </div>
  );
}

export default App;

子元素

js
function Son() {
  console.log("子元素渲染");
  return (
    <div>
      <h1> Son</h1>
    </div>
  );
}

export default Son;

这个时候你会发现只要 count 改变 父子组件一起渲染,浪费性能

改变子元素

  • 这样通过包裹层,当父元素改变的时候,子元素不会重新渲染
js
import React, { memo } from "react";
function Son() {
  console.log("子元素渲染");
  return (
    <div>
      <h1> Son</h1>
    </div>
  );
}

export default memo(Son);

useCallback

  • 适用于缓存方法

适用场景

  • memo 有个致命的缺点 就是当父元素给子元素传方法的时候,子元素还是会重新渲染

  • 这个时候就需要 useCallback 来包裹方法,从而减少子元素的渲染,

  • 两个参数 第一个缓存的方法,第二个是依赖项 要是不写则每次都会重新生成一个方法

父元素

js
import { useCallback, useReducer, useState } from "react";
import Son from "./Son";
function App() {
  const [count, setCount] = useState(0);
  console.log("父元素渲染");
  const handleClick = () => {
    setCount(count + 1);
  };
  const fn = () => {};
  return (
    <div>
      <h1 onClick={handleClick}>父元素--{count}</h1>
      <Son fn1={fn} />
    </div>
  );
}

export default App;

子元素

js
import React, { memo } from "react";
function Son() {
  console.log("子元素渲染");
  return (
    <div>
      <h1> Son</h1>
    </div>
  );
}

export default memo(Son);

注意

  • 这种情况 缓存失效了,因为每次父元素渲染的时候,都会重新生成一个 fn,所以子元素还是会重新渲染

改变父元素

js
import { useCallback, useReducer, useState } from "react";
import Son from "./Son";
function App() {
  const [count, setCount] = useState(0);
  console.log("父元素渲染");
  const handleClick = () => {
    setCount(count + 1);
  };
  // 缓存,依赖项必须写,不然每次都会重新生成一个方法
  const fn = useCallback(() => {}, []);
  return (
    <div>
      <h1 onClick={handleClick}>父元素--{count}</h1>
      <Son fn1={fn} />
    </div>
  );
}

export default App;

usememo

  • 适用于属性,也可用于方法

适用场景

  • memo 有个致命的缺点 就是当父元素给子元素传属性的时候,子元素还是会重新渲染

  • 这个时候就需要 useMemo 来包裹属性,从而减少子元素的渲染,

  • 两个参数 第一个缓存的方法,第二个是依赖项 要是不写则每次都会重新生成一个方法

父元素

js
import { useCallback, useReducer, useState, useMemo } from "react";
import Son from "./Son";
function App() {
  const [count, setCount] = useState(0);
  console.log("父元素渲染");
  const handleClick = () => {
    setCount(count + 1);
  };
  const arr = [1, 2, 3, 4, 5];
  return (
    <div>
      <h1 onClick={handleClick}>父元素--{count}</h1>
      <Son arr={arr} />
    </div>
  );
}

export default App;

子元素

js
import React, { memo } from "react";
function Son() {
  console.log("子元素渲染");
  return (
    <div>
      <h1> Son</h1>
    </div>
  );
}

export default memo(Son);

注意

  • 这种情况 缓存失效了,因为每次父元素渲染的时候,都会重新生成一个 arr,所以子元素还是会重新渲染

改变父元素

js
import { useCallback, useReducer, useState, useMemo } from "react";
import Son from "./Son";
function App() {
  const [count, setCount] = useState(0);
  console.log("父元素渲染");
  const handleClick = () => {
    setCount(count + 1);
  };
  const arr = useMemo(() => {
    return [1, 2, 3, 4, 5];
  }, []);
  return (
    <div>
      <h1 onClick={handleClick}>父元素--{count}</h1>
      <Son arr={arr} />
    </div>
  );
}

export default App;
  • 适用于方法
js
import { useCallback, useReducer, useState, useMemo } from "react";
import Son from "./Son";
function App() {
  const [count, setCount] = useState(0);
  console.log("父元素渲染");
  const handleClick = () => {
    setCount(count + 1);
  };
  const arr = useMemo(() => {
    return [1, 2, 3, 4, 5];
  }, []);
  const func1 = useMemo(() => {
    return function () {
      console.log("func1");
    };
  }, []);
  return (
    <div>
      <h1 onClick={handleClick}>父元素--{count}</h1>
      <Son arr={arr} func1={func1} />
    </div>
  );
}

export default App;