如何在React Hooks中使用componentWillMount()?


175

在React的官方文档中,它提到-

如果您熟悉React类的生命周期方法,则可以将useEffect Hook视为componentDidMount,componentDidUpdate和componentWillUnmount的组合。

我的问题是-我们如何componentWillMount()在钩子中使用lifecyle方法?

Answers:


336

你不能使用任何现有的生命周期方法(componentDidMountcomponentDidUpdatecomponentWillUnmount在钩等)。它们只能在类组件中使用。而且,使用Hooks只能在功能组件中使用。下面的行来自React文档:

如果你熟悉阵营类生命周期方法,你能想到的useEffect钩。因为componentDidMountcomponentDidUpdatecomponentWillUnmount结合。

建议是,您可以从功能组件中的类组件模仿这些生命周期方法。

componentDidMount安装组件时,内部代码运行一次。useEffect钩子等效于此行为是

useEffect(() => {
  // Your code here
}, []);

注意此处的第二个参数(空数组)。这将只运行一次。

如果没有第二个参数useEffect则会在每次渲染组件时调用该钩子,这很危险。

useEffect(() => {
  // Your code here
});

componentWillUnmount用于清理(例如删除事件侦听器,取消计时器等)。假设您要在其中添加事件监听器,componentDidMount并按componentWillUnmount如下所示将其删除。

componentDidMount() {
  window.addEventListener('mousemove', () => {})
}

componentWillUnmount() {
  window.removeEventListener('mousemove', () => {})
}

相当于上述代码的钩子如下

useEffect(() => {
  window.addEventListener('mousemove', () => {});

  // returned function will be called on component unmount 
  return () => {
    window.removeEventListener('mousemove', () => {})
  }
}, [])

183
对于其他Lifecycle事件的很好的解释,但这不是专门针对componentWillMount()的替代方案的问题。
设拉子

3
在PanResponder示例中,我已经看到componentWillMount似乎是必需的,否则您将获得未定义的panHandlers。
Dror Bar

2
我现在真的很了解该useEffect()功能,谢谢。
Iharob Al Asimi

67
为什么这是一个可接受的答案?您没有提到等效的吊钩componentWillMount
Mykybo '19

3
@techexpert该问题要求等效于componentWillMount,而不是componentWillUnmount。确实,这个答案根本不能回答问题,只是重申了OP已经暗示要知道的内容。
JoshuaCWebDeveloper

61

useComponentDidMount挂钩

在大多数情况下useComponentDidMount是要使用的工具。组件已安装(初始渲染)后,它将仅运行一次。

 const useComponentDidMount = func => useEffect(func, []);

useComponentWillMount

重要的是要注意,在类中组件componentWillMount被认为是遗留的。如果您需要代码在组件安装之前仅运行一次,则可以使用构造函数。在这里了解更多。由于功能组件没有构造函数的等效性,因此在某些情况下使用钩子在组件挂载前仅运行一次代码可能是有意义的。您可以使用自定义钩子来实现。

const useComponentWillMount = func => {
  const willMount = useRef(true);

  if (willMount.current) {
    func();
  }

  willMount.current = false;
};

但是,有一个陷阱。不要使用它来异步设置状态(例如在服务器请求之后。如您所料,它会影响初始渲染,而不会影响初始渲染)。此类情况应以处理useComponentDidMount

演示版

const Component = (props) => {
  useComponentWillMount(() => console.log("Runs only once before component mounts"));
  useComponentDidMount(() => console.log("Runs only once after component mounts"));
  ...

  return (
    <div>{...}</div>
  );
}

完整演示


18
这是唯一可以回答问题并具有意义的答案。谢谢!
chumakoff

3
唯一的问题是,由于涉及状态更新,您将获得额外的渲染。通过使用ref,您可以得到所需的行为而无需额外的渲染:`const useComponentWillMount = func => {const willMount = useRef(true); useEffect(()=> {willMount.current = false;},[]); 如果(willMount.current){func(); }; `
remix23

2
componentWillMount基于的此功能实现 useEffect有两个问题。第一个是功能组件中没有安装生命周期,两个挂钩都将在组件渲染后运行,因此Runs only once before component mounts具有误导性。第二个是componentWillMount在服务器渲染上调用的,useEffect不是。许多库仍然依赖于此,UNSAFE_componentWillMount因为当前这是触发服务器端副作用的唯一方法。
Paolo Moretti

2
@PaoloMoretti,谢谢。这个componentWillMount钩子与类组件中的componentWillMount生命周期并不完全等效。但是,传递给它的函数将仅在首次调用时立即运行。实际上,这意味着它将在呈现之前运行,甚至在首次返回值之前运行。我们可以同意吗?我同意使用名称componentWillMount是不理想的,因为该名称在类生命周期版本中具有某些含义。也许我最好称它为“ useRunPreMount”。
Ben Carp

1
@PaoloMoretti,我不太明白。我不使用SSR,但我的理解是,在SSR组件上,WillMount运行两次-一次在服务器上,一次在客户端上。我认为传递给useComponentDidMount的回调也是如此。useComponentDidMount在useEffect上中继以停止调用回调。在执行useEffect的回调之前,组件的功能将运行两次-一次在服务器上,一次在客户端上。不是这样吗
Ben Carp

53

根据reactjs.org所述,将来将不再支持componentWillMount。 https://reactjs.org/docs/react-component.html#unsafe_componentwillmount

无需使用componentWillMount。

如果要在组件安装之前做一些事情,只需在构造函数()中进行即可。

如果要发出网络请求,请不要在componentWillMount中进行。这是因为这样做会导致意外的错误。

网络请求可以在componentDidMount中完成。

希望能帮助到你。


于08/03/2019更新

您要求componentWillMount的原因可能是因为您想要在渲染之前初始化状态。

只需在useState中执行即可。

const helloWorld=()=>{
    const [value,setValue]=useState(0) //initialize your state here
    return <p>{value}</p>
}
export default helloWorld;

或者,例如,如果原始代码如下所示,则可能要在componentWillMount中运行一个函数:

componentWillMount(){
  console.log('componentWillMount')
}

使用钩子,您需要做的就是删除生命周期方法:

const hookComponent=()=>{
    console.log('componentWillMount')
    return <p>you have transfered componeWillMount from class component into hook </p>
}

我只想在有关useEffect的第一个答案中添加一些内容。

useEffect(()=>{})

useEffect在每个渲染器上运行,它是componentDidUpdate,componentDidMount和ComponentWillUnmount的组合。

 useEffect(()=>{},[])

如果我们在useEffect中添加一个空数组,则它仅在安装组件时运行。这是因为useEffect将比较传递给它的数组。因此它不必是一个空数组,它可以是不变的数组。例如,它可以是[1,2,3]或['1,2']。useEffect仍然仅在安装组件时运行。

是否要运行一次还是在每次渲染后运行取决于您。只要您知道自己在做什么,就忘记添加数组,这并不危险。

我创建了一个挂钩示例。请检查一下。

https://codesandbox.io/s/kw6xj153wr


于21/08/2019更新

自从我写下以上答案以来,这一直是白人。我认为您需要注意一些事项。使用时

useEffect(()=>{},[])

当react比较您传递给数组[]的值时,它将使用Object.is()进行比较。如果将对象传递给它,例如

useEffect(()=>{},[{name:'Tom'}])

这与以下内容完全相同:

useEffect(()=>{})

每次都会重新渲染,因为当Object.is()比较对象时,它将比较其引用而不是值本身。这与为什么{} === {}返回false的原因相同,因为它们的引用不同。如果仍要比较对象本身而不是引用,则可以执行以下操作:

useEffect(()=>{},[JSON.stringify({name:'Tom'})])

17
问题是如何用钩子实现它
Shubham Khatri

3
但是您不需要使用钩子来实现它,因为它不受支持。无需学习如何使用钩子进行操作。
MING WU

1
既然您已经提到componentDidMount是可以使用的正确生命周期,那么您可能已经添加了如何在答案中实现它,那么您的答案就会比公认的答案更有意义
Shubham Khatri

8
当然,这应该是可以接受的答案-它解释了钩子范式中ComponentWillMount不可用。在初始化功能组件被简化-它只是需要的部分功能
西拉

1
这和componentWillMount有什么关系?如果将代码扔到功能组件中,则它将运行每个渲染,不仅是在组件即将安装时。
覆盖

13

useLayoutEffect[]如果功能实际上类似于componentWillMount-它可以在第一个内容到达DOM之前运行,则可以使用一组空的观察者()完成此操作–尽管实际上有两个更新,但是它们在绘制到屏幕之前是同步的。

例如:


function MyComponent({ ...andItsProps }) {
     useLayoutEffect(()=> {
          console.log('I am about to render!');
     },[]);

     return (<div>some content</div>);
}

useState对于使用初始化程序/设置程序的好处,或者useEffect尽管它可以计算渲染过程,但用户不会注意到对DOM的实际重新渲染,而是第一个值得注意的渲染之前运行,而不是这样。useEffect。缺点当然是您的第一次渲染会稍有延迟,因为在绘画到屏幕之前必须进行检查/更新。不过,这确实取决于您的用例。

我个人认为,useMemo在某些需要进行繁重工作的特殊情况下,这很好-只要记住,这是例外而不是常规。


3
useLayoutEffect是必经之路!!!!该问题回答了我有关检查用户是否已登录的问题。(问题是,将加载组件,然后检查用户是否已登录。)我的问题是,这是标准做法吗?我没在太多地方见过
杰西卡(Jessica)

1
是的,这很普遍;在正式的React文档中也提到过-只是用较小的文字,因为双重DOM渲染的后果是为了在用户注意到之前运行逻辑。
rob2d

实际上,它在渲染组件之后运行。因此,它与componentWillMount完全不同。
吉里·米哈尔

6

我编写了一个自定义钩子,它将在第一次渲染之前运行一次函数。

useBeforeFirstRender.js

import { useState, useEffect } from 'react'

export default (fun) => {
  const [hasRendered, setHasRendered] = useState(false)

  useEffect(() => setHasRendered(true), [hasRendered])

  if (!hasRendered) {
    fun()
  }
}

用法:

import React, { useEffect } from 'react'
import useBeforeFirstRender from '../hooks/useBeforeFirstRender'


export default () => { 
  useBeforeFirstRender(() => {
    console.log('Do stuff here')
  })

  return (
    <div>
      My component
    </div>
  )
}

6

这是我使用useRef钩子在功能组件中模拟构造函数的方式:

function Component(props) {
    const willMount = useRef(true);
    if (willMount.current) {
        console.log('This runs only once before rendering the component.');
        willMount.current = false;        
    }

    return (<h1>Meow world!</h1>);
}

这是生命周期示例:

function RenderLog(props) {
    console.log('Render log: ' + props.children);
    return (<>{props.children}</>);
}

function Component(props) {

    console.log('Body');
    const [count, setCount] = useState(0);
    const willMount = useRef(true);

    if (willMount.current) {
        console.log('First time load (it runs only once)');
        setCount(2);
        willMount.current = false;
    } else {
        console.log('Repeated load');
    }

    useEffect(() => {
        console.log('Component did mount (it runs only once)');
        return () => console.log('Component will unmount');
    }, []);

    useEffect(() => {
        console.log('Component did update');
    });

    useEffect(() => {
        console.log('Component will receive props');
    }, [count]);


    return (
        <>
        <h1>{count}</h1>
        <RenderLog>{count}</RenderLog>
        </>
    );
}
[Log] Body
[Log] First time load (it runs only once)
[Log] Body
[Log] Repeated load
[Log] Render log: 2
[Log] Component did mount (it runs only once)
[Log] Component did update
[Log] Component will receive props

当然,类组件没有Body步骤,由于函数和类的概念不同,因此无法进行1:1仿真。


我没有深入探讨您的示例,但是您的第一个代码段对我有用,谢谢!
SAndriy

3

有一个很好的解决方法来实现componentDidMountcomponentWillUnmount使用useEffect

根据文档,useEffect可以返回“清理”功能。此函数将不会在第一个useEffect调用中被调用,仅在后续调用中被调用。

因此,如果我们使用useEffect完全不依赖的钩子,则仅在安装组件时才调用该钩子,而在卸载该组件时才调用“ cleanup”函数。

useEffect(() => {
    console.log('componentDidMount');

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

仅在卸载组件时才调用清除返回函数调用。

希望这可以帮助。


2
如果它与componentWillMount没有任何关系,它将如何帮助您?我想念什么吗?
ZenVentzi

是的,你缺少的是,在同一个事实useEffect叫你得到相同的功能componentWillMount,并componentWillUnmount在一个非常干净的方式
AfikDeri

事实并非如此,它useEffect仅在渲染之后运行,而componentWillMount在组件渲染之前运行。
代码进行覆盖

@Overcode我说的componentDidMount不是componentWillMount。我错过了这个问题,我很糟糕。
AfikDeri

1

您可以修改useMemo挂钩以模仿componentWillMount生命周期事件。做就是了:

const Component = () => {
   useMemo(() => {
     // componentWillMount events
   },[]);
   useEffect(() => {
     // componentWillMount events
     return () => {
       // componentWillUnmount events
     }
   }, []);
};

您需要先保留useMemo钩子,然后再与状态进行交互。这不是它的预期目的,但是它对所有componentWillMount问题都有效。

之所以有效,是因为useMemo不需要实际返回值,并且您不必实际将其用作任何值,但是由于它根据一个依赖项存储一个值,该依赖项仅运行一次(“ []”),并且其在我们组件的顶部运行当组件安装在其他组件之前时,它将运行一次。


0

https://reactjs.org/docs/hooks-reference.html#usememo

请记住,传递给useMemo的函数在渲染期间运行。不要在渲染时执行通常不会执行的任何操作。例如,副作用属于useEffect,而不是useMemo。


usememo用于性能优化。如果更改了道具,那么钩子将在安装后再次渲染,这违背了作者的意图。
max54

0

本·卡尔普(Ben Carp)的答案对我来说似乎只有一个。

但是,由于我们使用的是功能方式,因此可以从闭包和HoC中受益于另一种方法:

const InjectWillmount = function(Node, willMountCallback) {
  let isCalled = true;
  return function() {
    if (isCalled) {
      willMountCallback();
      isCalled = false;
    }
    return Node;
  };
};

然后使用它:

const YourNewComponent = InjectWillmount(<YourComponent />, () => {
  console.log("your pre-mount logic here");
});

0

原始问题的简短答案,如何componentWillMount与React Hooks一起使用:

componentWillMount弃用,并视为旧版。反应建议

通常,我们建议使用constructor()代替初始化状态。

现在,您可以在Hook FAQ中找到功能组件的类构造函数的等效项:

构造函数:功能组件不需要构造函数。您可以在useState调用中初始化状态。如果计算初始状态的成本很高,则可以将一个函数传递给useState。

因此,一个使用示例componentWillMount如下所示:

const MyComp = () => {
  const [state, setState] = useState(42) // set initial value directly in useState 
  const [state2, setState2] = useState(createInitVal) // call complex computation

  return <div>{state},{state2}</div>
};

const createInitVal = () => { /* ... complex computation or other logic */ return 42; };

0

只需在useEffect中添加一个空的Dependency数组,它将像一样工作componentDidMount

useEffect(() => {
  // Your code here
  console.log("componentDidMount")
}, []);

0

有一个简单的技巧来模拟componentDidMountcomponentWillUnmount通过使用useEffect

useEffect(() => {
  console.log("componentDidMount");

  return () => {
    console.log("componentWillUnmount");
  };
}, []);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.