ReactHooks 官网文档翻译

useCallback(fn, dependencies)

useCallback是一个React Hook,它允许您在重新渲染之间缓存函数定义。

const cachedFn = useCallback(fn, dependencies)

1、参数:

fn:要缓存的函数值。它可以接受任何参数并返回任何值。React将在初始渲染期间返回(而不是调用!)您的函数。在下一次渲染中,如果自上次渲染以来依赖项没有更改,React将再次为您提供相同的函数。否则,它将为您提供在当前渲染过程中传递的函数,并将其存储起来,以备以后重用。React不会调用您的函数。函数将返回给您,以便您可以决定何时以及是否调用它。

dependencies:fn代码内部引用的所有反应值的列表。反应值包括props、state以及直接在组件体内声明的所有变量和函数。如果您的linter配置为React,它将验证每个React值是否正确指定为依赖项。依赖项列表必须具有恒定数量的项,并且像[dep1,dep2,dep3]一样以内联方式编写。React将使用Object.is的比较算法将每个依赖项与其以前的值进行比较。

2、返回值:

在初始渲染时,useCallback返回您传递的fn函数。
在随后的渲染过程中,它将从上次渲染中返回一个已经存储的fn函数(如果依赖项没有更改),或者返回您在此渲染过程中传递的fn函数。

3、告诫:

useCallback是一个Hook,所以您只能在组件的顶层或您自己的Hook中调用它。不能在循环或条件内部调用它。如果需要,请提取一个新组件并将状态移动到其中。

4、用法:

在以下渲染中,React将把依赖项与您在上一次渲染中传递的依赖项进行比较。如果任何依赖项都没有更改(与Object.is相比),useCallback将返回与以前相同的函数。否则,useCallback将返回在此渲染中传递的函数。

import { useCallback } from 'react';

export default function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

默认情况下,当组件重新渲染时,React会递归地重新渲染其所有子级。这就是为什么当ProductPage使用不同的主题重新渲染时,ShippingForm组件也会重新渲染。这对于不需要太多计算即可重新渲染的组件来说很好。但是,如果验证了重新渲染的速度较慢,则可以通过将其包装在备忘录中,告诉ShippingForm在其道具与上次渲染相同时跳过重新渲染

function ProductPage({ productId, referrer, theme }) {
  // ...
  return (
    <div className={theme}>
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );

使用了memo包裹子组件只有prop变化时才会重新渲染,否则就跳过重新渲染

import { memo } from 'react';

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});

在JavaScript中,函数(){}或()=>{}总是创建不同的函数,类似于{}对象文字总是创建新对象的方式。通常情况下,这不会是一个问题,但这意味着ShippingForm道具将永远不会相同,您的备忘录优化也不会起作用。在这个例子中父组件的prop变化时函数会重新创建,即使子组件使用memeo依然不起作用,因为handleSubmit每次都不同

function ProductPage({ productId, referrer, theme }) {
  // Tell React to cache your function between re-renders...
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // ...so as long as these dependencies don't change...

  return (
    <div className={theme}>
      {/* ...ShippingForm will receive the same props and can skip re-rendering */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

现在我们就可以将他等着在useCallback,只有依赖项变化时才会返回新的函数定义。一般情况下我们不必将函数使放在useCallback中,除非处于某种特定原因

function ProductPage({ productId, referrer, theme }) {
  // Tell React to cache your function between re-renders...
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // ...so as long as these dependencies don't change...

  return (
    <div className={theme}>
      {/* ...ShippingForm will receive the same props and can skip re-rendering */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

5、useCallack和useMemo 的区别

useMemo缓存调用函数的结果。useCallback缓存函数本身。

 const requirements = useMemo(() => { // Calls your function and caches its result
    return computeRequirements(product);
  }, [product]);

  const handleSubmit = useCallback((orderDetails) => { // Caches your function itself
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

可以想象成这样:

// Simplified implementation (inside React)
function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}

6、何时使用useCallack

如果你的应用程序像这个网站,并且大多数交互都很粗糙(比如替换一个页面或整个部分),那么通常不需要记忆。另一方面,如果你的应用程序更像一个绘图编辑器,并且大多数交互都是细粒度的(比如移动的形状),那么你可能会发现记忆非常有用。
使用useCallback缓存函数只有在少数情况下才有价值:
1、您将函数作为餐厨传递给了memo包裹的子组件
2、您传递的函数稍后将用作某个Hook的依赖项。例如,包装在useCallback中的另一个函数依赖于它,或者您依赖于useEffect中的此函数。
在其他情况下,用useCallback包装函数没有任何好处。这样做也没有重大危害,所以一些团队选择不考虑个别案例,并尽可能多地记忆。缺点是代码的可读性变差。此外,并非所有的记忆都是有效的:一个“总是新的”值就足以破坏整个组件的记忆。

useContext(SomeContext)

useContext是一个React Hook,它允许您从组件中读取和订阅上下文。

import { useContext } from 'react';

function MyComponent() {
  const theme = useContext(ThemeContext);
  // ...

1、参数

SomeContext:您之前使用createContext创建的上下文。

2、返回值

useContext返回调用组件的上下文值,返回的值始终是最新的

3、告诫

相应的需要位于执行useContext()调用的组件之上。

4、用法

深层数据传递、通过context传递更新数据
react 会找桑钱上下文最近的provider,不论层级多深都可以使用useContext(ThemeContext),访问到

function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  // ... renders buttons inside ...
}

useEffect(setup, dependencies?)

useEffect是一个React Hook,用于将组件与外部系统同步。

import { useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverUrl, roomId]);
  // ...
}

1、参数

setup:具有Effect逻辑的函数。您的设置函数也可以选择返回一个清理函数。
可选依赖项:设置代码内部引用的所有反应值的列表。反应值包括props、state以及直接在组件体内声明的所有变量和函数。

2、用法

连接到外部系统:某些组件在页面上显示时,需要保持与网络、某些浏览器API或第三方库的连接。这些系统不受React控制,所以它们被称为外部系统。
例如:
一个由setInterval()和clearInterval.()管理的计时器。
使用window.addEventListener()和window.removeEventLister()的事件订阅。
带有API的第三方动画库,如animation.start()和animation.reset()

3、解释

感觉官网的解释有些晦涩,一般我们是这么理解的
可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
情况一:不传第二个参数(模拟 class 组件的 DidMount 和 DidUpdate )

useEffect(() => {
/** 执行逻辑 */
}) 

情况二:传递一个空数组(模拟 class 组件的 DidMount )

useEffect(() => {
/** 执行逻辑 */
},[]) 

情况三:传递数组有依赖项(模拟 class 组件的 DidUpdate )

useEffect(() => {
/** 执行逻辑 */
},[age,name])  

情况四:第一个参数可以返回一个回调函数(模拟 WillUnMount 组件销毁的时候 停止计时器 )

const [time, setTime] = useState(0)
useEffect(() => {
    const InterVal = setInterval(() => {
        setTime(time + 1)
    },1000)
    return () => {
           clearInterval(InterVal )
       }
},[])

useMemo(calculateValue, dependencies)

useMemo是一个React Hook,可以在重新渲染之间缓存计算结果。

import { useMemo } from 'react';

function TodoList({ todos, tab }) {
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab]
  );
  // ...
}

1、参数

calculateValue:计算要缓存的值的函数。
dependencies:calculateValue代码内部引用的所有反应值的列表。

2、返回值

在初始呈现时,useMemo返回不带参数的调用calculateValue的结果。
在接下来的渲染过程中,它将返回上次渲染中已经存储的值(如果依赖项没有更改),或者再次调用calculateValue,并返回calculateValue返回的结果。

3、用法

跳过昂贵的计算

4、怎样算昂贵的计算

一般来说,除非您正在创建或循环数千个对象,否则这可能并不昂贵。如果你想获得更多的信心,你可以添加一个控制台日志来测量在一段代码中花费的时间:

console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');

如果记录的总时间加起来相当大(比如1毫秒或更长),那么将计算结果记忆起来可能是有意义的。作为实验,您可以将计算结果封装在useMemo中,以验证该交互的总记录时间是否减少。

5、何时使用useMemo

1、计算较慢,并且依赖值很少变化
2、将该函数结果作为参数传给memo包裹的子组件
3、该结果作为其他hook的依赖项
在其他情况下,将计算包装在useMemo中没有任何好处。这样做也没有重大危害,所以一些团队选择不考虑个别案例,并尽可能多地记忆。这种方法的缺点是代码的可读性变差

useState(initialState)

useState是一个React Hook,它允许您将状态变量添加到组件中。

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [todos, setTodos] = useState(() => createTodos());
  // ...

1、参数

initialState:您希望状态初始为的值。它可以是任何类型的值,但函数有一种特殊的行为。此参数在初始渲染后将被忽略。
如果将函数作为initialState传递,它将被视为初始值设定项函数。

2、返回值

current state :当前状态,在第一次渲染期间,它将与您通过的initialState相匹配。
set函数:用于将状态更新为不同的值并触发重新渲染。您可以直接传递下一个状态,也可以传递根据上一个状态计算的函数

setName('Taylor');
  setAge(a => a + 1);

3、用法

向组件添加状态,React将存储下一个状态,使用新值再次渲染组件,并更新UI。

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(42);
  const [name, setName] = useState('Taylor');
  // ...

4、定义对象

您可以将对象和数组置于状态。在React中,状态被认为是只读的,所以您应该替换它,而不是突变现有的对象。

//  Don't mutate an object in state like this:
form.firstName = 'Taylor';
// ✅ Replace state with a new object
setForm({
  ...form,
  firstName: 'Taylor'
});

5、避免传入函数调用结果为初始值

 const [todos, setTodos] = useState(createInitialTodos());

尽管createInitialTodos()的结果仅用于初始渲染,但您仍在每次渲染时调用此函数。如果创建大型数组或执行昂贵的计算,这可能是浪费。

  const [todos, setTodos] = useState(createInitialTodos);

要解决此问题,可以将其作为初始值设定项函数传递给useState您传递的是函数本身createInitialTodos,而不是调用它的结果createInitialTodos()。如果您将函数传递给useState,React只会在初始化期间调用它。

useRef(initialValue)

useRef是一个React Hook,它允许您引用渲染不需要的值。

mport { useRef } from 'react';

function MyComponent() {
  const intervalRef = useRef(0);
  const inputRef = useRef(null);
  // ...

1、参数

initialValue:您希望ref对象的当前属性初始为的值。它可以是任何类型的值。此参数在初始渲染后将被忽略。

2、返回值

current:最初,它被设置为您传递的initialValue。您可以稍后将其设置为其他内容。

3、用法

①使用ref定义一个无需引起页面重新渲染的状态
改变一个ref的值不会触发重新渲染。这意味着引用非常适合存储不影响组件视觉输出的信息。

function handleStopClick() {
  const intervalId = intervalRef.current;
  clearInterval(intervalId);
}

②使用ref操作DOM

使用ref来操作DOM是特别常见的。React对此有内置支持。
首先,声明一个初始值为null的ref对象:

import { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef(null);
  // ...
  return <input ref={inputRef} />;

React创建DOM节点并将其放在屏幕上后,React将把ref对象的当前属性设置为该DOM节点。现在,您可以访问<input>的DOM节点,并调用focus()等方法:
③ 避免重新创建引用内容
React将初始ref值保存一次,并在下次渲染时忽略它。

function Video() {
  const playerRef = useRef(new VideoPlayer());

尽管新VideoPlayer()的结果仅用于初始渲染,但您仍在每次渲染时调用此函数。如果创建昂贵的对象,这可能是浪费。
要解决此问题,您可以按如下方式初始化ref:

function Video() {
  const playerRef = useRef(null);
  if (playerRef.current === null) {
    playerRef.current = new VideoPlayer();
  }
  // ...

4、将ref传给自定义组件

默认情况下,组件不会将其DOM节点公开给父组件,这么写会报错

const inputRef = useRef(null);
return <MyInput ref={inputRef} />;

如果您希望MyInput的父组件能够访问<input>DOM节点,则必须选择使用forwardRef:

import { forwardRef } from 'react';

const MyInput = forwardRef(({ value, onChange }, ref) => {
  return (
    <input
      value={value}
      onChange={onChange}
      ref={ref}
    />
  );
});

export default MyInput;

useImperativeHandle(ref, createHandle, dependencies?)

1、解释

React的useImperativeHandle是自定义钩子,可以让父组件获取并执行子组件内某些自定义函数(方法),或者获取子组件的状态。

2、本质

useImperativeHandle本质上其实是子组件将自己内部的函数(方法)通过useImperativeHandle添加到父组件中useRef定义的对象中。如果想使用useImperativeHandle,那么还要结合useRef、React.forwardRef一起使用。

3、示例

结合这个例子:
使用forwardRef后父组件可以访问子组件的Dom

import { forwardRef } from 'react';

const MyInput = forwardRef(({ value, onChange }, ref) => {
  return (
    <input
      value={value}
      onChange={onChange}
      ref={ref}
    />
  );
});

export default MyInput;

假设您不想公开整个<input>DOM节点,但希望公开它的两个方法:focus和scrollIntoView。要做到这一点,请将真实的浏览器DOM保存在一个单独的引用中。然后使用useImperativeHandle只使用您希望父组件调用的方法来公开句柄:

import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input {...props} ref={inputRef} />;
});

现在,如果父组件获得对MyInput的引用,它将能够调用其上的焦点和scrollIntoView方法。但是,它将无法完全访问底层的<input>DOM节点。

你可能感兴趣的:(react,react.js,前端,javascript)