hooks Documentation

A high-quality & reliable React Hooks library.

14,953 stars TypeScript TypeScript 1 file · ~8,051 tokens #ahooks#hooks-library#react#react-hooks#umi-hooks
RAW Doc

File: README.md

📚 Documentation

Notice

use-url-state is now published as @ahooks.js/use-url-state.

If you are installing or upgrading this package, please use the new package name.

✨ Features

  • Easy to learn and use
  • Supports SSR
  • Special treatment for functions, avoid closure problems
  • Contains a large number of advanced Hooks that are refined from business scenarios
  • Contains a comprehensive collection of basic Hooks
  • Written in TypeScript with predictable static types

📦 Install

bash
$ npm install --save ahooks
# or
$ yarn add ahooks
# or
$ pnpm add ahooks
# or
$ bun add ahooks

🔨 Usage

ts
import { useRequest } from "ahooks";

💻 Online Demo

🤝 Contributing

bash
$ git clone [email protected]:alibaba/hooks.git
$ cd hooks
$ pnpm run init
$ pnpm start

Open your browser and visit http://127.0.0.1:8000

We welcome all contributions, please read our CONTRIBUTING.MD first, let's build a better hooks library together.

Thanks to all the contributors:

👥 Discuss


File: docs/guide/blog/function.en-US.md

ahooks function specification

ahooks tries its best to help everyone avoid the closure problem by specially processing the input and output functions.

1. All the output functions of ahooks, the references are stable

ts
const [state, setState] = useState();

As we all know, the reference of the setState function returned by React.useState is fixed, and there is no need to consider weird problems when using it, and there is no need to put setState in the dependencies of other Hooks.

All functions returned by ahooks Hooks have the same characteristics as setState, the reference will not change, just feel free to use it.

2. For all user input functions, always use the latest one

For the received function, ahooks will do a special process to ensure that the function called each time is always the latest.

ts
const [state, setState] = useState();

useInterval(() => {
  console.log(state);
}, 1000);

For example, in the above example, the function called by useInterval at any time is always the latest, that is, the state is always the latest.

Principle

For the input function, we use useRef to make a record to ensure that the latest function can be accessed anywhere.

js
const fnRef = useRef(fn);
fnRef.current = fn;

For example, the useUnmount code is as follows:

js
const useUnmount = (fn) => {
  const fnRef = useRef(fn);
  fnRef.current = fn;

  useEffect(
    () => () => {
      fnRef.current();
    },
    [],
  );
};

In the above code, because we use ref for memorizing the latest function to solve the closure problem.

For the output function, we use the useMemoizedFn wrapped to ensure that the reference address will never change.

For a simple example, given a useToggle Hook, the code is like this:

js
const useToggle = (left, right) => {
  const [state, setState] = useState(left);

  const toggle = useCallback(() => {
    setState((s) => (s === left ? right : left));
  }, [left, right]);

  return [state, toggle];
};

The toggle function returned in this demo will change according to the changes of left/right, which is uncomfortable for users to use.

Then we replace useCallback with useMemoizedFn to realize that the toggle reference will never change.

js
const useToggle = (left, right) => {
  const [state, setState] = useState(left);

  const toggle = useMemoizedFn(() => {
    setState((s) => (s === left ? right : left));
  });

  return [state, toggle];
};

File: docs/guide/blog/function.zh-CN.md

ahooks 函数处理规范

ahooks 通过对输入输出函数做特殊处理,尽力帮助大家避免闭包问题。

1. ahooks 所有的输出函数,地址都是不会变化的

ts
const [state, setState] = React.useState();

众所周知,React.useState 返回的 setState 函数地址是固定的,使用时不需要考虑奇奇怪怪的问题,不需要把 setState 放到各种依赖中。

ahooks 所有 Hooks 返回的函数,都拥有和 setState 一样的特性,地址不会变化,放心大胆的使用即可。

2. 所有用户输入的函数,永远使用最新的一份

对于接收的函数,ahooks 会做一次特殊处理,保证每次调用的函数永远是最新的。

ts
const [state, setState] = useState();

useInterval(() => {
  console.log(state);
}, 1000);

比如以上示例,useInterval 任何时候调用的函数永远是最新的,也就是 state 永远是最新的。

实现原理

针对输入函数,我们通过 useRef 做一次记录,以保证在任何地方都能访问到最新的函数。

js
const fnRef = useRef(fn);
fnRef.current = fn;

比如 useUnmount 代码如下:

js
const useUnmount = (fn) => {
  const fnRef = useRef(fn);
  fnRef.current = fn;

  useEffect(
    () => () => {
      fnRef.current();
    },
    [],
  );
};

在上面的代码中,由于我们通过 ref 来记忆最新的函数,解决闭包问题。

针对输出函数,我们通过 ahooks 的 useMemoizedFn 包裹,保证地址永远不会变化。

举一个比较简单的例子,假如我们有一个 useToggle Hook,代码是这样的

js
const useToggle = (left, right) => {
  const [state, setState] = useState(left);

  const toggle = useCallback(() => {
    setState((s) => (s === left ? right : left));
  }, [left, right]);

  return [state, toggle];
};

这个 demo 中返回的 toggle 函数,会根据 left/right 的变化而变化的,用户用起来很难受。

然后我们将 useCallback 替换成 useMemoizedFn,即可实现 toggle 地址永远不变化。

js
const useToggle = (left, right) => {
  const [state, setState] = useState(left);

  const toggle = useMemoizedFn(() => {
    setState((s) => (s === left ? right : left));
  });

  return [state, toggle];
};

File: docs/guide/blog/hmr.en-US.md

React Hooks & react-refresh(HMR)

What is react-refresh?

react-refresh-webpack-plugin is a hot module replacement (HMR) plugin provided by React.

A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.

In the development, react-refresh can keep state in component, and only change the edited part. In umi, can enable this feature by config fastRefresh: {}.

This gif shows the development experience of using the react-refresh. After edit some code, the username and password that have been filled in remain unchanged, only the edited part has been changed.

Simple Principles of react-refresh

For the Class component, react-refresh are always refresh (remount), existing state will be reset. For function components, react-refresh retains the existing state. Therefore, react-refresh provides a better experience for function components.

This article mainly explains the weird behavior of React Hooks in react-refresh mode. Now let us look at the working mechanism of react-refresh on function components.

  • To maintain the state during hot replacement, the value of useState and useRef will not update.
  • During hot replacement, To avoid some problems, useEffect、useCallback、useMemoRun will re-executed.

When we update the code, we need to "clean up" the effects that hold onto past values (e.g. passed functions), and "setup" the new ones with updated values. Otherwise, the values used by your effect would be stale and "disagree" with value used in your rendering, which makes Fast Refresh much less useful and hurts the ability to have it work with chains of custom Hooks.

As shown in the gif, after the text is modified, state remains unchanged and useEffect is executed again.

Problem caused by react-refresh

Under the above working mechanism, there will be many problems. Next, I will give a few specific examples.

First problem

js
import React, { useEffect, useState } from 'react';

export default () => {
  const [count, setState] = useState(0);

  useEffect(() => {
    setState((s) => s + 1);
  }, []);

  return {count};
};

The above code is very simple. In normal mode, the maximum value of count is 1. Because useEffect will only be executed once during initialization.

But in the react-refresh mode, the state does not change every time it is hot updated, but the re-execution of useEffect will cause the value of count to keep increasing.

As shown in the gif, count increases with each hot replacement.

Second problem

If you used ahooks v2 or react-use useUpdateEffect will also have unexpected behavior in HMR.

javascript
import React, { useEffect } from 'react';
import useUpdateEffect from './useUpdateEffect';

export default () => {
  useEffect(() => {
    console.log('useEffect');
  }, []);

  useUpdateEffect(() => {
    console.log('useUpdateEffect');
  }, []);

  return hello world;
};

Compared with useEffect, useUpdateEffect ignores the first execution and only executes when the deps changes. In the normal mode of the above code, useUpdateEffect will never be executed, because deps is an empty array and will never change.
But in react-refresh mode, during HMR, useUpdateEffect and useEffect are executed at the same time.

The reason for this problem is that useUpdateEffect uses ref to record whether it is currently executed for the first time, see the code below.

javascript
import { useEffect, useRef } from 'react';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isMounted = useRef(false);

  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return effect();
    }
  }, deps);
};

export default useUpdateEffect;

The key of the above code is isMounted.

  • During initialization, after the useEffect is executed, the isMounted is changed to true
  • After the HMR, when the useEffect is re-executing, because the isMounted is already true, so the whole effect is executed again.

Third problem

The first time discovered this problem is the useRequest of ahooks, after HMR, the loading would always be true. After an inspection, the reason is use the isUnmount ref to mark whether the component is unmount.

javascript
import React, { useEffect, useState } from 'react';

function getUsername() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('test');
    }, 1000);
  });
}

export default function IndexPage() {
  const isUnmount = React.useRef(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    getUsername().then(() => {
      if (isUnmount.current === false) {
        setLoading(false);
      }
    });
    return () => {
      isUnmount.current = true;
    };
  }, []);

  return loading ? loading : hello world;
}

As the code above, during the hot replacement, isUnmount.current becomes true, causing the code to think that the component has been unmounted during the second execution.

How to solve these problems

First solution

The first solution is to solve it from the code, that is, when we write code, we can always remember the weird behavior in react-refresh mode.

For example, with useUpdateEffect, we can initialize the isMounted ref during initialization or hot replacement. as follows:

diff
import { useEffect, useRef } from 'react';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isMounted = useRef(false);

+  useEffect(() => {
+  	isMounted.current = false;
+  }, []);

  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return effect();
    }
  }, deps);
};

export default useUpdateEffect;

This solution is effective for both questions 2 and 3 above.

Second solution

According to Official Document, we can solve this problem by adding the following comment in the file .

javascript
/* @refresh reset */

After adding this question, every hot replacement will remount, that is, the component will be re-executed. useState and useRef will also be reset, so the above problem will not occur.

Official attitude

There are already many unspoken rules for React Hooks. When using react-refresh, there are still unspoken rules to pay attention to. But the official reply stated that this is expected behavior, see the issue.

Effects are not exactly "mount"/"unmount" — they're more like "show"/"hide".


File: docs/guide/blog/hmr.zh-CN.md

React Hooks & react-refresh(HMR)

什么是 react-refresh

react-refresh-webpack-plugin 是 React 官方提供的一个 模块热替换(HMR)插件。

A Webpack plugin to enable "Fast Refresh" (also previously known as Hot Reloading) for React components.

在开发环境编辑代码时,react-refresh 可以保持组件当前状态,仅仅变更编辑的部分。在 umi 中可以通过 fastRefresh: {}快速开启该功能。

这张 gif 动图展示的是使用 react-refresh 特性的开发体验,可以看出,修改组件代码后,已经填写的用户名和密码保持不变,仅仅只有编辑的部分变更了。

react-refresh 的简单原理

对于 Class 类组件,react-refresh 会一律重新刷新(remount),已有的 state 会被重置。而对于函数组件,react-refresh 则会保留已有的 state。所以 react-refresh 对函数类组件体验会更好。
本篇文章主要讲解 React Hooks 在 react-refresh 模式下的怪异行为,现在我来看下 react-refresh 对函数组件的工作机制。

  • 在热更新时为了保持状态,useState 和 useRef 的值不会更新。
  • 在热更新时,为了解决某些问题,useEffect、useCallback、useMemo 等会重新执行。

When we update the code, we need to "clean up" the effects that hold onto past values (e.g. passed functions), and "setup" the new ones with updated values. Otherwise, the values used by your effect would be stale and "disagree" with value used in your rendering, which makes Fast Refresh much less useful and hurts the ability to have it work with chains of custom Hooks.

如上图所示,在文本修改之后,state 保持不变,useEffect 被重新执行了。

react-refresh 工作机制导致的问题

在上述工作机制下,会带来很多问题,接下来我会举几个具体的例子。

第一个问题

js
import React, { useEffect, useState } from 'react';

export default () => {
  const [count, setState] = useState(0);

  useEffect(() => {
    setState((s) => s + 1);
  }, []);

  return {count};
};

上面的代码很简单,在正常模式下,count值最大为 1。因为 useEffect 只会在初始化的时候执行一次。
但在 react-refresh 模式下,每次热更新的时候,state 不变,但 useEffect 重新执行,就会导致 count 的值一直在递增。

如上图所示,count 随着每一次热更新在递增。

第二个问题

如果你使用了 ahooks v2 或者 react-use 的 useUpdateEffect,在热更新模式下也会有不符合预期的行为。

javascript
import React, { useEffect } from 'react';
import useUpdateEffect from './useUpdateEffect';

export default () => {
  useEffect(() => {
    console.log('执行了 useEffect');
  }, []);

  useUpdateEffect(() => {
    console.log('执行了 useUpdateEffect');
  }, []);

  return hello world;
};

useUpdateEffect 与 useEffect相比,它会忽略第一次执行,只有在 deps 变化时才会执行。以上代码的在正常模式下,useUpdateEffect 是永远不会执行的,因为 deps 是空数组,永远不会变化。
但在 react-refresh 模式下,热更新时,useUpdateEffect 和 useEffect 同时执行了。

造成这个问题的原因,就是 useUpdateEffect 用 ref 来记录了当前是不是第一次执行,见下面的代码。

javascript
import { useEffect, useRef } from 'react';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isMounted = useRef(false);

  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return effect();
    }
  }, deps);
};

export default useUpdateEffect;

上面代码的关键在 isMounted

  • 初始化时,useEffect 执行,标记 isMounted 为 true
  • 热更新后,useEffect 重新执行了,此时 isMounted 为 true,就往下执行了

第三个问题

最初发现这个问题,是 ahooks 的 useRequest 在热更新后,loading 会一直为 true。经过分析,原因就是使用 isUnmount ref 来标记组件是否卸载。

javascript
import React, { useEffect, useState } from 'react';

function getUsername() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('test');
    }, 1000);
  });
}

export default function IndexPage() {
  const isUnmount = React.useRef(false);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    getUsername().then(() => {
      if (isUnmount.current === false) {
        setLoading(false);
      }
    });
    return () => {
      isUnmount.current = true;
    };
  }, []);

  return loading ? loading : hello world;
}

如上代码所示,在热更新时,isUnmount.current 变为了 true,导致二次执行时,代码以为组件已经卸载了,不再响应异步操作。

如何解决这些问题

方案一

第一个解决方案是从代码层面解决,也就是要求我们在写代码的时候,时时能想起来 react-refresh 模式下的怪异行为。
比如 useUpdateEffect 我们就可以在初始化或者热替换时,将 isMounted ref 初始化掉。如下:

diff
import { useEffect, useRef } from 'react';

const useUpdateEffect: typeof useEffect = (effect, deps) => {
  const isMounted = useRef(false);

+  useEffect(() => {
+  	isMounted.current = false;
+  }, []);

  useEffect(() => {
    if (!isMounted.current) {
      isMounted.current = true;
    } else {
      return effect();
    }
  }, deps);
};

export default useUpdateEffect;

这个方案对上面的问题二和三都是有效的。

方案二

根据官方文档,我们可以通过在文件中添加以下注释来解决这个问题。

javascript
/* @refresh reset */

添加这个问题后,每次热更新,都会 remount,也就是组件重新执行。useState 和 useRef 也会重置掉,也就不会出现上面的问题了。

官方态度

本来 React Hooks 已经有蛮多潜规则了,在使用 react-refresh 时,还有潜规则要注意。但官方回复说这是预期行为,见该 issue。

Effects are not exactly "mount"/"unmount" — they're more like "show"/"hide".


File: docs/guide/blog/ssr.en-US.md

React Hooks & SSR

Server-Side Rendering refers to the page processing technology where the HTML structure of the page is spliced on the server side. Generally used to solve SEO problems and speed up the first screen.

Since SSR executes JS code in a non-browser environment, there will be many problems. This article mainly introduces the common problems and solutions of React Hooks in SSR mode.

Problem 1: DOM/BOM is missing

SSR is to run React code in a node environment, while global properties such as window, document, and navigator are not available at this time. If you use these properties directly, you will get errors like window is not defined, document is not defined, navigator is not defined, etc.

A common misuse is that global properties, such as document, are used directly during the execution of Hooks.

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

export default () => {
  const [state, setState] = useState(document.visibilityState);
  return state;
};

Solution

  1. Put the code of accessing the DOM/BOM in useEffect/useLayoutEffect (the server will not execute it) to avoid errors when the server executes, for example:
js
import React, { useState, useEffect } from 'react';

export default () => {
  const [state, setState] = useState();

  useEffect(() => {
    setState(document.visibilityState);
  }, []);

  return state;
};
  1. Differentiate the environments by isBrowser
js
import React, { useState } from 'react';

function isBrowser() {
  return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
}

export default () => {
  const [state, setState] = useState(isBrowser() && document.visibilityState);

  return state;
};

Problem 2: useLayoutEffect Warning

If useLayoutEffect is used, the following warning will appear in SSR mode

⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.

Solution

  1. Use useEffect instead of useLayoutEffect (nonsense)
  2. Dynamically specify whether to use useEffect or useLayoutEffect according to the environment. This is a hack solution from the community, currently in react-redux, react-use, react-beautiful-dnd.
js
import { useLayoutEffect, useEffect } from 'react';
const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;
export default useIsomorphicLayoutEffect;

Summary: Need to pay attention when writing Hooks

  1. Do not use DOM/BOM properties directly in non-useEffect/useLayoutEffect
  2. When using DOM/BOM properties other than useEffect/useLayoutEffect, use isBrowser to determine whether to execute in the browser environment
  3. If a Hook needs to receive DOM/BOM properties, it needs to support passing the properties via a function type parameter. Take the useEventListener of ahooks as an example, it must support the function type to specify the target option.
diff
import React, { useState } from 'react';
import { useEventListener } from 'ahooks';

export default () => {
  const [value, setValue] = useState(0);

  const clickHandler = () => {
    setValue(value + 1);
  };

  useEventListener(
    'click',
    clickHandler,
    {
-       target: document.getElementById('click-btn')
+       target: () => document.getElementById('click-btn')
    }
  );

  return (
    
      You click {value} times
    
  );
};
  1. Use useIsomorphicLayoutEffect instead of useLayoutEffect

Reference


File: docs/guide/blog/ssr.zh-CN.md

React Hooks & SSR

服务端渲染(Server-Side Rendering),是指由服务侧完成页面的 HTML 结构拼接的页面处理技术。一般用于解决 SEO 问题和首屏加载速度问题。

由于 SSR 是在非浏览器环境执行 JS 代码,所以会出现很多问题。本文主要介绍 React Hooks 在 SSR 模式下常见问题及解决方案。

更多关于 SSR 的介绍可以看 UmiJS 的文档《服务端渲染(SSR)》。

问题一:DOM/BOM 缺失

SSR 是在 node 环境下运行 React 代码,而此时 window、document、navigator 等全局属性没有。如果直接使用了这些属性,就会报错 window is not defined, document is not defined, navigator is not defined 等。

常见的错误用法是在 Hooks 执行过程中,直接使用了 document 等全局属性。

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

export default () => {
  const [state, setState] = useState(document.visibilityState);
  return state;
};

解决方案

  1. 将访问 DOM/BOM 的方法放在 useEffect/useLayoutEffect 中(服务端不会执行),避免服务端执行时报错,例如:
js
import React, { useState, useEffect } from 'react';

export default () => {
  const [state, setState] = useState();

  useEffect(() => {
    setState(document.visibilityState);
  }, []);

  return state;
};
  1. 通过 isBrowser 来做环境判断
js
import React, { useState } from 'react';

function isBrowser() {
  return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
}

export default () => {
  const [state, setState] = useState(isBrowser() && document.visibilityState);

  return state;
};

问题二 useLayoutEffect Warning

如果使用了 useLayoutEffect,在 SSR 模式下,会出现以下警告

⚠️ Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.

解决方案

  1. 使用 useEffect 代替 useLayoutEffect(废话)
  2. 根据环境动态的指定是使用 useEffect 还是 useLayoutEffect。这是来自社区的一种 hack 解决方案,目前在 react-redux、react-use、react-beautiful-dnd 均使用的这种方案。
js
import { useLayoutEffect, useEffect } from 'react';
const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect;
export default useIsomorphicLayoutEffect;

总结:写 Hooks 时需要注意

  1. 不要在非 useEffect/useLayoutEffect 中,直接使用 DOM/BOM 属性
  2. 在非 useEffect/useLayoutEffect 使用 DOM/BOM 属性时,使用 isBrowser 判断是否在浏览器环境执行
  3. 如果某个 Hook 需要接收 DOM/BOM 属性,需要支持函数形式传参。以 ahooks 的 useEventListener 举例,必须支持函数形式来指定 target 属性。
diff
import React, { useState } from 'react';
import { useEventListener } from 'ahooks';

export default () => {
  const [value, setValue] = useState(0);

  const clickHandler = () => {
    setValue(value + 1);
  };

  useEventListener(
    'click',
    clickHandler,
    {
-       target: document.getElementById('click-btn')
+       target: () => document.getElementById('click-btn')
    }
  );

  return (
    
      You click {value} times
    
  );
};
  1. 使用 useIsomorphicLayoutEffect 来代替 useLayoutEffect

参考资料


File: docs/guide/blog/strict.en-US.md

React Hooks & strict mode

What is strict mode

In React, there are many historical APIs or writing methods that will be obsolete in future versions and are now marked as deprecated. such as componentWillMount, in normal mode, you can use it normally. But in strict mode, a warning will be thrown:

So strict mode is for future development, all APIs or writing methods that are not recommended will throw warnings (only effective in development mode).

We can use React.StrictMode to enable strict mode.

javascript
import React from 'react';

function ExampleApplication() {
  return (
    
      <Header />
      <React.StrictMode>
        
          <ComponentOne />
          <ComponentTwo />
        
      </React.StrictMode>
      <Footer />
    
  );
}

For more documents, please refer to "Strict Mode"

Points to note in React Hooks

One of the most important capabilities of strict mode is "Detecting Unexpected Side Effects", in the upcoming concurrent mode, the component is divided into two phases:

  • Render phase: Generate DOM tree, will execute constructor, componentWillMount, componentWillReceiveProps, componentWillUpdate, getDerivedStateFromProps, shouldComponentUpdate, render, useState, useMemo, useCallback and other life cycles
  • Commit stage: Apply DOM changes, trigger componentDidMount, componentDidUpdate, useEffect and other life cycles

Generally, the render phase is time-consuming, and the commit phase is executed quickly. Therefore, in the upcoming concurrent mode, the render phase may be suspended and re-executed. That is, the life cycle of the rendering phase may be executed multiple times.

javascript
constructor(){
  services.getUserInfo().then(() => {
    .....
  });
}

As above, if we initiate a network request in the constructor, it may be executed multiple times. So do not perform operations with side effects during the render phase.

But if you perform side-effect operations during the rendering phase, React will not be able to perceive it. But in strict mode, React will intentionally repeat the render phase method, making it easier for us to find such bugs in the development phase (not all the life cycles of the rendering phase will be re-executed, see Official Documentation).

javascript
const useTest = () => {
  const [state, setState] = useState(() => {
    console.log('get state');
    return 'state';
  });

  const memoState = useMemo(() => {
    console.log('get memo state');
    return 'state';
  }, []);

  console.log('render');

  return state;
};

In the above code, the first parameter of useState, useMemo and the Hook function body are all executed twice.

Demo

Please remember the conclusion: In strict mode, the first parameter of useState, useMemo, useReducer and the Hook function body will be executed twice. Do not perform operations with side effects here.


File: docs/guide/blog/strict.zh-CN.md

React Hooks & strict mode

什么是严格模式

在 React 中,有很多历史的 API 或写法,在未来版本中会被废弃,现在被标记为不建议使用。既然是不建议使用,那还是可以用的,比如 componentWillMount,在普通模式下,你可以正常使用。但在严格模式下,就会抛出警告:

所以严格模式就是面向未来开发,所有不建议的 API 或写法,都会抛出警告(只在开发模式生效)。

一般我们可以通过 React.StrictMode 来局部启用严格模式。

javascript
import React from 'react';

function ExampleApplication() {
  return (
    
      <Header />
      <React.StrictMode>
        
          <ComponentOne />
          <ComponentTwo />
        
      </React.StrictMode>
      <Footer />
    
  );
}

更多文档参考《严格模式》

在 React Hooks 中需要注意的点

严格模式很重要的一个能力是《检测意外的副作用》,在未来的 concurrent 模式中,组件被分为两个阶段:

  • 渲染(render)阶段:生成 DOM 树,会执行 constructor、componentWillMount、componentWillReceiveProps、componentWillUpdate、getDerivedStateFromProps、shouldComponentUpdate、render、useState、useMemo、useCallback 等生命周期
  • 提交(commit)阶段:操作 DOM,触发 componentDidMount、componentDidUpdate、useEffect 等生命周期

一般渲染阶段会比较耗时,提交阶段执行很快。所以在未来的 concurrent 模式中,渲染阶段可能会被暂停、重新执行。也就是渲染阶段的生命周期,可能会被多次执行。

javascript
constructor(){
  services.getUserInfo().then(() => {
    .....
  });
}

如上,我们在 constructor 中发起网络请求,就可能被执行多次。所以不要在渲染阶段执行带有副作用的操作。

但假如你在渲染阶段执行了副作用操作,React 也是无法感知的。但是 React 在严格模式下,会故意重复执行渲染阶段的方法,使得我们在开发阶段能更容易发现这类 bug(并不是所有渲染阶段的生命周期都会被重新执行,具体见官方文档)。

javascript
const useTest = () => {
  const [state, setState] = useState(() => {
    console.log('get state');
    return 'state';
  });

  const memoState = useMemo(() => {
    console.log('get memo state');
    return 'state';
  }, []);

  console.log('render');

  return state;
};

在上面的代码中 useState、useMemo 的第一个参数、Hook 函数体均执行了两次。

在线体验

请记住结论:在严格模式下,useState、useMemo、useReducer 的第一个参数、Hook 函数体都会被执行两次,不要在这里执行带有副作用的操作。


File: docs/guide/dom.en-US.md

Hooks of DOM specification

Most of the DOM Hooks will receive the target parameter, which indicates the element to be processed.

target supports three types React.MutableRefObject, HTMLElement, () => HTMLElement.

  1. Support React.MutableRefObject
tsx
export default () => {
  const ref = useRef(null);
  const isHovering = useHover(ref);
  return {isHovering ? "hover" : "leaveHover"};
};
  1. Support HTMLElement
tsx
export default () => {
  const isHovering = useHover(document.getElementById("test"));
  return {isHovering ? "hover" : "leaveHover"};
};
  1. Support () => HTMLElement, generally applicable in SSR scenarios
tsx
export default () => {
  const isHovering = useHover(() => document.getElementById("test"));
  return {isHovering ? "hover" : "leaveHover"};
};

In addition, the target of DOM Hooks supports dynamic changes. for example:

tsx
export default () => {
  const [boolean, { toggle }] = useBoolean();

  const ref = useRef(null);
  const ref2 = useRef(null);

  const isHovering = useHover(boolean ? ref : ref2);
  return (
    <>
      {isHovering ? "hover" : "leaveHover"}
      {isHovering ? "hover" : "leaveHover"}
    </>
  );
};