React Hook 详解下

159 阅读4分钟

useEffect

副作用
对环境的改变即为副作用

用途
作为componentDidMount使用,[]作为第二个参数
作为componentDidUpdate使用,可指定依赖
作为componentWillUnmount使用,通过return
以上三种可同时存在

如果同时存在多个useEffect,会按照出现顺序执行

import React, {useState, useEffect} from "react";
import ReactDOM from "react-dom";

function App(){
  const [n, setN] = useState(0);
  const onClick = () =>{
    setN( i => i + 1);
  };
  useEffect(() => {
    console.log("第一次渲染之后执行这一句话");
  },[]) 
  //[]里面的变量变化时执行,[]表示除了第一次渲染,之后不会执行
  useEffect(() => {
    if(n !== 0){
      console.log("n变化了");
    }
  },[n]) 
  //在n变化时执行
  useEffect(() => {
    console.log("任何一个state变化时都执行");
  })
  //不写参数,就在任何一个state变化时都执行
  useEffect(() => {
    const id = setInterval(() => {
      console.log('hi')
    }, 1000)
     //return是告诉react当组件消失时执行这里的代码,对环境有了任何变动,走的时候清理掉垃圾
    return () => {
      window.clearInterval(id)
    }
  },[])
  return (
    <div>
      n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

按顺序执行
 useEffect(() => { console.log("1"); })
 useEffect(() => { console.log("2"); })
打印出1,2

useLayoutEffect

布局副作用
useEffect        //在浏览器渲染完成后执行
useLayoutEffect  //在浏览器渲染前执行

import React, { useState, useEffect, useLayoutEffect } from "react";
import ReactDOM from "react-dom";
const BlinkyRender = () => {
  const [value, setValue] = useState(0);
  useEffect(() => {
    document.querySelector('#x').innerText = `value: 1000`
  }, [value]); 
  //当value更新之后(包含第一次),把div的innerText变成1000,先执行渲染,后执行函数
  useLayoutEffect(() => {
    document.querySelector('#x').innerText = `value: 1000`
  }, [value]); 
  //先执行代码,先变成1000,再去渲染,省略了初次渲染
  return (
    <div id="x" onClick={() => setValue(0)}>value: {value}</div>
  );
};
ReactDOM.render( <BlinkyRender />, document.querySelector("#root");

useLayoutEffect 总是比 useEffect  先执行
useEffect(() => { console.log("1"); })
useLayoutEffect(() => { console.log("2"); })
打印出2,1

useMemo

用来缓存一些,希望在2次新旧组件迭代的时候,用上一次的值

第一个参数是 ()=>value
第二个人参数是依赖[m,n]
只有当依赖变化时,才会计算出新的value; 如果依赖不变,那么就重用之前的value

如果你的value是个函数,就要写成一个返回函数的函数
useMemo( ()=> (x) => console.log(x))

React.memo:React默认有多余的render,添加监听函数之后,会从新执行监听函数,生产新的函数
新旧函数虽然功能一样,但地址不一样,所以要从新渲染

使用useMemo

import React, { useMemo } from "react";
import ReactDOM from "react-dom";
function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  const onClick2 = () => {
    setM(m + 1);
  };
  //使用useMemo,接受一个函数,这个函数的返回值就是你要缓存的东西
  const onClickChild = useMemo(() => {
    const fn = div => {
      console.log("on click child, m: " + m);
      console.log(div);
    };
    return fn;
  }, [m]);
  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
        <button onClick={onClick2}>update m {m}</button>
      </div>
      <Child2 data={m} onClick={onClickChild} />
    </div>
  );
}

function Child(props) {
  return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

uesCallback

使用uesCallback代替useMemo

useMemo( ()=> (x) => console.log(x), [m])
等价于
uesCallback(x => console.log(x), [m])

useRef

useRef,可以用来引用DOM对象,也可以用来引用普通对象
如果你需要一个值,在组件不断render时保持不变,使用useRef

初始化:const.count = useRef(0)
读取:  count.current
使用current,为了保证两次useRef是同一个值(只有引用可以做到)

useRef不能变化时自动render,需要手动render

import React, { useRef, useEffect } from "react";
import ReactDOM from "react-dom";

function App(){
  const count = useRef(0);
  const [n, setN] = React.useState(0);
  const [_n, _setN] = React.useState(null);
  const onClick = () => {
    setN(n + 1);
  }
  //手动更新count
  const onClick2 = () => {
   _ setN(n + 1);
  }
//每次渲染完之后,就把count+=1,每次渲染完count都是不变的,是同一个count
//current才是真正的值,count是包裹器
  useEffect(() =>{
    count.current += 1;
  });
  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
        <button onClick={onClick2}>update count: {count.current}</button>//更新count按钮
      </div>
    </div>
  )
}
const rootElement = document.getElementById("root");

forwardRef

函数组件props无法传递ref属性,需要使用forwardRef把函数组件包起来

import React, {useRef} from "react";
import ReactDOM from "react-deo";
function App(){
  //buttonRef是随着渲染不会变的量
  const buttonRef = useRef(null);
  return(
    <div className="App">
   //使用buttonRef引用到Button3对相应的DOM对象
      <Button3 ref={buttonRef}>按钮</Button3>
    </div>
  );
}
const Button3 = React.forwarRef((props, ref) => {
  return <button className="red" ref={ref} {...props} />;
})
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useImperativeHandle

自定义ref,如果一个函数组件暴露一个ref给外面,可以使用useImperativeHandle自定义ref

import React, {useRef, useState, useEffect, useImperativeHandle,createRef} from "react";
import ReactDOM from "react-dom";
function App() {
  const buttonRef = useRef(null);
  useEffect(() => {
    console.log(buttonRef.current);
  });
  return (
    <div className="App">
      <Button2 ref={buttonRef}>按钮</Button2>
      <button className="close" onClick={() => {console.log(buttonRef);
          buttonRef.current.x();}}>x</button>
    </div>
  );
}

const Button2 = React.forwardRef((props, ref) => {
  const realButton = createRef(null);
  const setRef = useImperativeHandle;
  //自定义ref
  setRef(ref, () => {
    return {
      x: () => {
        realButton.current.remove();
      },
      realButton: realButton
    };
  });
  return <button ref={realButton} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Stale Closure(过时闭包)

function createIncrementFixed(i) {
  let value = 0;
  function increment() {
    value += i;
    console.log(value);
    return function logValue() {
      const message = `Current value is ${value}`;
      console.log(message);
    };
  }
  return increment;
}

const inc = createIncrementFixed(1);
const log = inc(); // logs 1
inc();             // logs 2
inc();             // logs 3
log();             // logs "Current value is 3"
log()是一个过时的,每次value += i;
都会产生新的message,但log却对应着第一次产生的message,log没有更新

解决办法
function WatchCount() {
  const [count, setCount] = useState(0);
  useEffect(function() {
    const id = setInterval(function log() {
      console.log(`Count is: ${count}`);
    }, 2000);
    return function() {
      //清除旧的id
      clearInterval(id);
    }
  }, [count]);
   //[count]绑定依赖,只要count更新,就会得到一个新的log
  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1) }>
        Increase
      </button>
    </div>
  );
}

详细资料点击:React内置Hook API