React Hook(todo)

React Hook

useRef

作用

useRef返回一个包含current属性的对象,并且改变此对象的值不会导致重新渲染。

useRef通常用于以下三种场景:

  • 保存一个值,以便在下次重新渲染时仍能获取该值

  • 获取DOM节点

  • 避免重新创建引用内容

保存一个值

useRef()保存一个值,并在后面的重新渲染中仍能获取当前的值。

import React, {useState, useRef} from 'react';

export function App(props) {
  const [val,setVal] = useState(0)
  const s = useRef(1)
  const a = {current: 0}

   setInterval(()=>{
     setVal(val+1)
     s.current += 1
     a.current += 1
     console.log(s,a)
   },3000);

  return (
    <div className='App'>
      <div> {val} </div>
    </div>
  );
}

获取DOM节点

如果要获取真实的DOM节点,那么需要:

  1. useRef()初始值设置为null

  2. 将ref对象传递到JSX 节点的ref属性上

  3. ref对象的current属性就指向了该DOM节点,如果节点被删除或不存在,则current为null。

import React, {useState, useRef} from 'react';

export function App(props) {
  const divRef = useRef(null)
  console.log(divRef)

  return (
    <div className='App'>
      <div ref={divRef}></div>
    </div>
  );
}

避免重新创建

React 会保存一次初始 ref 值,并在下一次渲染时忽略它。

import React, {useState, useRef} from 'react';

function createData(){
  console.log("执行了createData函数")
  return 1
}

export function App(props) {
  const [val, setVal] = useState(0) 
  // 每次重新渲染都会执行createData,造成一定的性能浪费
  // const a = useRef(createData())
  // 可以通过useRef这样来避免重复创建
  const a = useRef(null)
  if(a.current === null){
    a.current = createData();
  }

  // 调用setVal来触发重新渲染
  setInterval(()=>{
    setVal(val+1);
  },1000)

  return (
    <div className='App'>
      <div></div>
    </div>
  );
}

useMemo

useMemo是一个react Hook,它的作用是在重新渲染时缓存计算结果,来提升性能。

避免昂贵的重新计算

某些时候我们可能会有某个计算量非常庞大的函数,这个函数执行起来非常的慢,理想情况下是将这个计算的结果缓存起来,只有当某些变量改变时才执行函数重新计算,这个时候就可以使用useMemo。这其实和vue的computed相似。

function slowFunction(num) {
    for (let i = 0; i < 10000; i++) {
      for (let i = 0; i < 10000; i++) {}
    }
    return num * 10;
  }

 const numValue = slowFunction(num);
function slowFunction(num) {
    for (let i = 0; i < 10000; i++) {
      for (let i = 0; i < 10000; i++) {}
    }
    return num * 10;
  }

 const numValue = useMemo(() => slowFunction(num), [num]);

第一种情况,每次组件重新渲染时都会导致slowFunction函数重新计算,但是在第二种情况下,react会缓存slowFunction的计算结果,只有当num改变时,才会重新计算。

useMemo所带来的性能提升可以查看下面示例:

https://codesandbox.io/s/usememobi-mian-ang-gui-de-chong-xin-ji-suan-ilf1dt

避免子组件重新渲染

当父组件的Props或者state改变时,会导致组件自身和其所有子孙组件都重新渲染,但是有时候我们不需要让子组件重新渲染,这个时候可以使用memo ,通过memo缓存组件,如果该组件的所有Prop都没有改变,则不会重新渲染该组件。

// App.jsx
import "./styles.css";
import Child from "./Child";
import { useState } from "react";

export default function App() {
  const [re, reRender] = useState(1);

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => reRender((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child text="使用memo" />
      </div>
    </div>
  );
}

// Child.jsx
import { memo } from "react";

function Child({ text }) {
  console.log("子组件渲染");

  return <div>Child: {text + "的组件"}</div>;
}

export default memo(Child);

https://codesandbox.io/s/memobi-mian-zu-jian-chong-xin-xuan-ran-ydmlgo

useCallback

useCallback是一个react Hook,用于在重新渲染期间缓存函数声明。

useCallback需要传递两个参数:一个需要被缓存的函数和一个依赖数组。

react会通过Object.is来比较上一次渲染时的依赖数组的每一项与当前的值是否相同,如果全部相同则会返回上一次的函数,否则useCallback会返回此次渲染的函数。

跳过组件重新渲染

之前说过react可以通过使用memo 来跳过组件重新渲染,以此来优化性能。

// App.jsx
import { useState, memo } from "react";

const Child = memo(function ({ text }) {
  console.log("子组件渲染");

  return <div>Child: {text + "的组件"}</div>;
});

export default function App() {
  const [re, reRender] = useState(1);

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => reRender((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child text="使用memo" />
      </div>
    </div>
  );
}

只有当组件Child的props有更改的情况下才会重新渲染,否则不会重新渲染组件。

但是有时候memo并不会按照我们想象的情况来工作,当我们传递一个函数作为prop到Child组件时,由于每次App组件重新渲染都会重新执行app组件函数,遇到函数声明或者箭头函数等都会创建一个新的函数,这时memo通过Object.is比较两个函数时会认为prop更改了,就会导致memo优化失效。

import { useState, memo } from "react";

const Child = memo(function ({ fn}) {
  return <div>Child</div>;
});

export default function App() {
	const [num, setNum] = useState(1)
  // 每当App函数重新执行时遇到函数声明都会创建一个新的函数
  function fn(num){ return num * 2 }

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => setNum((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child fn={fn} />
      </div>
    </div>
  );
}

这个时候需要利用useCallback缓存函数,将num作为依赖项,只有当num改变时才会重新执行函数声明,创建一个新的函数。

import { useState, memo } from "react";

const Child = memo(function ({ fn}) {
  return <div>Child</div>;
});

export default function App() {
	const [num, setNum] = useState(1)
  // 只有当num改变时才会重新执行函数声明创建一个新的函数。
  const fn = useCallback(function fn(num){ return num * 2 }, [num])

  console.log("父组件更新");

  return (
    <div className="App">
      <h1>避免子组件重新渲染</h1>
      <button onClick={() => setNum((n) => n + 1)}>组件重新渲染</button>
      <div>
        <Child fn={fn} />
      </div>
    </div>
  );
}

从缓存函数中更新状态

有时,我们可能需要在缓存回调函数中基于之前的值来更新状态。

import { useState, useCallback } from "react";

function Child({ todo }) {
  const [num, setNum] = useState(1);
  const fn = useCallback(
    function () {
      const n = num;
      setNum(n + todo);
    },
    [num]
  );

  return (
    <div>
      <h3>num: {num}</h3>
      <button onClick={fn}>num+1</button>
    </div>
  );
}

这看起来和useState更新状态很像,例如:

const fn = () => {
  setNum((n) =>  n + todo);
};

这也能基于num上一次渲染的值来更新num状态,但是这里的todo却并不是上一次渲染的值,如果我们需要根据上一次渲染的todonum来更新num,我们需要useCallback

 // 只有当num更新后,缓存函数中的todo才会获取最新值。
 const fn = useCallback(
    function () {
      const n = num;
      setNum(n + todo);
    },
    [num]
  );

https://codesandbox.io/s/usecallbackcong-huan-cun-han-shu-zhong-geng-xin-zhuang-tai-upzqft

避免Effect频繁执行

以下面的代码为例:

import "./styles.css";
import { useEffect, useState } from "react";

export default function App() {
  const [re, reRender] = useState(0);
	const [roomId, setRoomId] = useState(0);

  function createOptions() {
    return {
      serverUrl: "https://localhost:1234",
      roomId: roomId
    };
  }

  useEffect(() => {
    const options = createOptions();
    // ...
    console.log("useEffect执行");
  }, [createOptions]);

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button onClick={() => reRender((n) => n + 1)}>重新渲染</button>
    </div>
  );
}

当组件重新渲染后,会声明一个新的createOptions函数,这就导致了useEffect会重复执行。

const createOptions = useCallback(
  function createOptions() {
    return {
      serverUrl: "https://localhost:1234",
      roomId
    };
  },
  [roomId]
);

useEffect(() => {
  const options = createOptions();
  // ...
  console.log("useEffect执行");
}, [createOptions]);

当然,也可以直接将函数声明放到useEffect中。

useEffect(() => {
  function createOptions() {
    return {
      serverUrl: "https://localhost:1234",
      roomId
    };
  }
  const options = createOptions();
  // ...
  console.log("useEffect执行");
}, [roomId]);

useReducer

最后更新于