React Hooks

一、基础的State Hook和Effect Hook

第一段代码

function Myfunc() {
  const [count, setCount] = useState(0);//State Hook

  useEffect(() => {//Effect Hook
    const interval = setInterval(() => {
      setCount((c) => c + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return <span>{count}</span>;
}
① State Hook

浅显的理解,state hook通过解构赋值的方法把0赋值给count状态,把类似于class组件中setState的方法传递给setCount

② Effect Hook

Effect Hook可以让你在函数组件中执行副作用操作。useEffect可以看作componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

useEffect中的回调函数中的函数体(不包括return后面的函数),相当于定义了componentDidMountcomponentDidUpdate 两个方法。而return的函数相当于componentWillUnmount。在第三个副标题里面会详细介绍。

二、用useReducer处理复杂的数据

第二段代码

function countReducer(state, action) {
  switch (action.type) {
    case "add":
      return state + 1;
    case "minus":
      return state - 1;
    default:
      return state;
  }
}

function Myfunc2() {
  const [count, dispatchCount] = useReducer(countReducer, 0);
  useEffect(() => {
    const interval = setInterval(() => {
      dispatchCount({ type: "minus" });
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return <span>{count}</span>;
}

虽然此处的state很简单,但是有可能你的state是一个很复杂的数据结构,那么可以尝试使用useReducerAPI

三、深入理解Effect Hook第二个数组参数

第三段代码

function Myfunc3() {
  const [name, setName] = useState("zh-d");
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("effect invoked");
    return () => {
      console.log("effect deteched");
    };
  });

  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={setCount((c) => c + 1)}>{count}</button>
    </div>
  );
}
① 现象1

运行这段代码,在浏览器中,控制台会先输出"effect invoked"。然后,无论你是在input框中输入文字还是button上面点击,控制台都会先输出”effect deteched“,然后再输出”invoked“

② 原因1

首先,"effect invoked"相当于componentDidMount。然后,无论我们点击还是输入,都会触发state的改变(count或者name改变),这样的话Myfunc3这个组件就要重新渲染。就相当于componentWillUnmount+componentDidUpdate

第四段代码

function Myfunc3() {
  const [name, setName] = useState("zh-d");
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("effect invoked");
    return () => {
      console.log("effect deteched");
    };
  },[]);//传入了一个空数组

  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={setCount((c) => c + 1)}>{count}</button>
    </div>
  );
}

这段代码和第三段代码的区别是useEffect这个Hook传入了一个[]作为参数。

③ 现象2

运行这段代码,在浏览器中,控制台会先输出"effect invoked"。然后,无论你怎么操作state,控制台都不会打印出任何内容。只有你切换到其他页面,控制台会打印”effect deteched“

④ 原因2

首先,"effect invoked"相当于componentDidMount。后续点击或者输入没有触发componentWillUnmountcomponentDidUpdate

第五段代码

function Myfunc3() {
  const [name, setName] = useState("zh-d");
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("effect invoked");
    return () => {
      console.log("effect deteched");
    };
  },[name]);//传入了一个name

  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={setCount((c) => c + 1)}>{count}</button>
    </div>
  );
}

这段代码和第四段代码的区别是useEffect这个Hook的数组中传入了一个name作为参数。

⑤ 现象3

运行这段代码,在浏览器中,控制台会先输出"effect invoked"。然后,你在input输入框中输入内容时控制台会依次打印”effect deteched“和”effect invoked“。而你点击button的时候就什么都没有打印。

⑥ 原因3

useEffect只依赖了name,只有name改变才会触发componentWillUnmount+compontDidUpdate

⑦ 总结
(1)若你什么都不传,默认传递组件中所有state,任何state的更新都会触发useEffect的回调(componentWillUnmount+compontDidUpdate)
(2)若你传入一个空数组,就相当于什么state都没传,没有任何state的更新能够触发回调(componentWillUnmount+compontDidUpdate)
(3)若你传递一个数组且在数组中包含了state数据,那么只有数组中包含的state改变才会触发回调(componentWillUnmount+compontDidUpdate)
四、为什么在useEffect中调用setCount时不能直接使用count这个值

通过刚刚副标题三里面讲的内容可以知道,若你传入了一个空数组作为useEffect的第二个参数,则会触发useEffect里面count的更新。

如下,setCount传递的count+1的count指向的还是第一次渲染的0

function Myfunc() {
  const [count, setCount] = useState(0);//State Hook

  useEffect(() => {//Effect Hook
    const interval = setInterval(() => {
      setCount(count + 1);//这里的count永远为0
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return <span>{count}</span>;//这里的是最新值  
}
① 解决方法一

在数组中传入依赖[count]

但这样的话每次重新渲染渲染都会调用useEffect,更新count的值,但浪费性能

② 解决方法二

在setCount中传入箭头函数©=>c+1,c会自动拿到最新的count

② 解决方法三

useRef(下文会讲到)

五、useLayoutEffect

用法和useEffect一样,作用也差不多

永远在useEffect前一步调用。useLayoutEffect会在更新dom之前调用,而useEffectdom更新之后调用。一般都用useEffect而少用useLayoutEffect,因为useLayoutEffect会影响到dom更新。必须等待useLayoutEffect执行完毕才会更新dom。偶尔需要在dom更新之前调用。

六、Context-Hooks
function Root() {
  return (
    <div className="root">
      <MyContext.Provider value="aaa">
        <Myfunc />
      </MyContext.Provider>
    </div>
  );
}

function UseContext() {
  const myContext = useContext(MyContext);

  return (
    <span>
      <p>{myContext}</p> //打印出aaa
    </span>
  );
}

Context-Hooks相当于class组件里面的context api,如果不知道这个,也可以类比于react-redux来理解

七、Ref-Hook
function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef();

  useEffect(() => {
    console.log("get");
    const interval = setInterval(() => {
      console.log(countRef.current.innerHTML);
      setCount(+countRef.current.innerHTML + 1);
    }, 1000);
    return () => {
      console.log("bye");
      return clearInterval(interval);
    };
  }, []);

  return <div ref={countRef}>{count}</div>;
}

Ref-Hook能拿到Dom节点最新的那个属性

八、使用memo、useMemo和useCallback组合拳对Hooks渲染优化

第一段代码:不使用memo

function Parent() {
  const [name, setName] = useState("aaa");
  const [count, setCount] = useState(0);
  const config = {
    text: `count is ${count}`,
    color: count > 3 ? "red" : "blue",
  };
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <Child config={config} onButtonClick={() => setCount((count) => count + 1)} />
    </div>
  );
}

function Child({ onButtonClick, config }) {
  console.log("child render");
  return (
    <button onClick={onButtonClick} style={{ color: config.color }}>
      {config.text}
    </button>
  );
}

点击或者输入时都会打印出"child render",因为child组件不依赖于name和onButtonClick里面的回调函数,我们希望输入时不打印"child render",这是我们要用memo优化的点。

第二段代码:使用memo和useMemo以及useCallback

import React, { useState, useEffect, memo, useMemo, useCallback } from "react";

export default function Parent() {
  const [name, setName] = useState("aaa");
  const [count, setCount] = useState(0);
  const config = useMemo(
    () => ({
      text: `count is ${count}`,
      color: count > 3 ? "red" : "blue",
    }),
    [count]
  );

  const handleButtonClick = useCallback(
    () => setCount((count) => count + 1),
    []
  );

  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <Child config={config} onButtonClick={handleButtonClick} />
    </div>
  );
}

const Child = memo(({ onButtonClick, config }) => {
  console.log("child render");
  return (
    <button onClick={onButtonClick} style={{ color: config.color }}>
      {config.text}
    </button>
  );
});

只有输入时会打印出"child render",通过useMemo解决了其他对象的变化,通过useCallBack解决了函数的变化影响。

九 闭包问题

当你点击一次handleClick然后再点击两次handleClick2,输出的结果会是10而不是12

import { useState } from "react";

export default function Bt() {
  const [count, setCount] = useState(10);
  const handleClick = () => {
    setTimeout(() => {
      alert(count);
    }, 2000);
  };
  const handleClick2 = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <button onClick={handleClick}>alert button</button>
      <button onClick={handleClick2}>change button</button>
      <span>{count}</span>
    </div>
  );
}

如果要显示更新之后的值,请使用useRef

import { useState, useRef } from "react";

export default function Bt() {
  const countRef = useRef();
  const [count, setCount] = useState(10);
  const handleClick = () => {
    setTimeout(() => {
      alert(countRef.current.innerHTML);
    }, 2000);
  };
  const handleClick2 = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <button onClick={handleClick}>alert button</button>
      <button onClick={handleClick2}>change button</button>
      <span ref={countRef}>{count}</span>
    </div>
  );
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值