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

How to use componentWillMount() in React Hooks?

在 React 的官方文档中提到 -

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

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

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

不需要使用componentWillMount。

如果你想在组件挂载之前做一些事情,就在constructor()中做。

如果要进行网络请求,请不要在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')
}

有了hook,只需要去掉生命周期方法即可:

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

我只想对第一个关于 useEffect 的回答补充一些内容。

useEffect(()=>{})

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

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

如果我们在 useEffect 中添加一个空数组,它 运行 就在组件安装时。这是因为 useEffect 会比较你传递给它的数组。 所以它不一定是空的 array.It 可以是不变的数组。例如,它可以是 [1,2,3] 或 ['1,2']。挂载组件时 useEffect 仍然只有 运行s。

这取决于您是希望它 运行 仅一次还是在每次渲染后 运行 秒。只要知道自己在做什么,忘记添加数组也没有危险。

我为 hook 创建了一个示例。请检查一下。

https://codesandbox.io/s/kw6xj153wr


更新于 21/08/2019

好久没写上面的回答了。我认为您需要注意一些事情。 当你使用

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

当react比较你传递给数组[]的值时,它使用Object.is()来比较。 如果你传递一个对象给它,比如

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

这与:

完全相同
useEffect(()=>{})

它每次都会重新渲染,因为当Object.is()比较一个对象时,它比较的是它的引用,而不是值本身。这与为什么 {}==={} returns false 是一样的,因为它们的引用不同。 如果你仍然想比较对象本身而不是引用,你可以这样做:

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

2021 年 7 月 9 日更新:

一些关于依赖的更新:

一般来说,如果你使用一个函数或一个对象作为依赖,它总是会重新渲染。但是 React 已经为你提供了解决方案:useCallback 和 useMemo

useCallback 可以记住一个函数。 useMemo 可以记住一个对象。

查看这篇文章:

https://javascript.plainenglish.io/5-useeffect-infinite-loop-patterns-2dc9d45a253f

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

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

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

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', () => {})
  }
}, [])

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

Remember that the function passed to useMemo runs during rendering. Don’t do anything there that you wouldn’t normally do while rendering. For example, side effects belong in useEffect, not useMemo.

useLayoutEffect 可以用一组空的观察者 ([]) 来实现这一点,如果功能实际上类似于 componentWillMount —— 它会在第一个之前 运行内容到达 DOM -- 虽然实际上有两个更新,但它们在绘制到屏幕之前是同步的。

例如:


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

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

使用 initializer/setter 或 useEffect 优于 useState 的好处是虽然它可以计算渲染通道,但没有实际重新渲染到 DOM用户会注意到,运行 之前是第一个明显的渲染,useEffect 不是这种情况。缺点当然是您的第一次渲染略有延迟,因为 check/update 必须在绘制到屏幕之前发生。不过,这确实取决于您的用例。

我个人认为,useMemo 在一些您需要做一些繁重的事情的特殊情况下很好——只要您牢记它是例外而不是常态。

useComponentWillMount 钩子

const useComponentWillMount = (cb) => {
    const willMount = useRef(true)

    if (willMount.current) cb()

    willMount.current = false
}

当出现顺序问题时(例如 运行ning 在另一个脚本之前),这个挂钩可能是一个救星。如果不是这种情况,请使用更符合 React hooks 范例的 useCompnponentDidMount。

useComponentDidMount 钩子

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

如果您知道您的效果在开始时应该只 运行 一次,请使用此解决方案。它只会在组件安装后 运行 一次。

useEffect范式

Class 组件具有生命周期方法,这些方法被定义为组件时间轴中的点。 Hooks 不遵循这种范式。相反,效果应该由它们的内容构成。

function Post({postID}){
  const [post, setPost] = useState({})

  useEffect(()=>{
    fetchPosts(postID).then(
      (postObject) => setPost(postObject)
    )
  }, [postID])

  ...
}

在上面的示例中,效果涉及获取 post 的内容。而不是某个时间点,它有一个它依赖的值 - postID。每次 postID 得到一个新值(包括初始化)它会重新 运行.

组件将挂载讨论

在 class 组件中,componentWillMount 被认为是遗留的 (source 1, source2)。它是遗留的,因为它可能 运行 不止一次,并且有一个替代方案 - 使用构造函数。这些注意事项与功能组件无关。

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

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>
  )
}

使用 useEffect.

实现 componentDidMountcomponentWillUnmount 有一个很好的解决方法

根据文档,useEffect 可以 return 一个 "cleanup" 函数。此函数不会在第一次 useEffect 调用时调用,只会在后续调用时调用。

因此,如果我们使用完全没有依赖关系的useEffect钩子,只有在挂载组件时才会调用钩子,而在卸载组件时调用"cleanup"函数。

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

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

清理 return 函数调用仅在卸载组件时调用。

希望对您有所帮助。

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");
});

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

componentWillMountdeprecated and considered legacy. React recommendation:

Generally, we recommend using the constructor() instead for initializing state.

现在在 Hook FAQ 你会发现,函数组件的 class 构造函数的等价物是:

constructor: Function components don’t need a constructor. You can initialize the state in the useState call. If computing the initial state is expensive, you can pass a function to 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; };

这是我使用 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

当然Class个组件没有Body个步骤,1:1由于函数的概念和类的不同无法模拟类。

只需简单地在 useEffect 中添加一个空的依赖数组,它将作为 componentDidMount

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

您可以破解 useMemo 挂钩以模仿 componentWillMount 生命周期事件。 只是做:

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

您需要在任何与您的状态交互之前保留 useMemo 挂钩。这不是它的本意,但它对我解决所有 componentWillMount 问题都有效。

这是可行的,因为 useMemo 不需要实际 return 一个值,你不必实际使用它作为任何东西,但因为它会记住一个基于依赖关系的值,它只会 运行 一次( “[]”) 和它在我们的组件之上它 运行s once 当组件先于其他任何东西安装时。

大多数人可能很清楚,但请记住,在函数组件主体内部调用的函数充当 beforeRender。这没有回答 ComponentWillMount 上的 运行 代码(在第一次渲染之前)的问题,但由于它是相关的并且可能对其他人有帮助,所以我将其留在这里。

const MyComponent = () => {
  const [counter, setCounter] = useState(0)
  
  useEffect(() => {
    console.log('after render')
  })

  const iterate = () => {
    setCounter(prevCounter => prevCounter+1)
  }

  const beforeRender = () => {
    console.log('before render')
  }

  beforeRender()

  return (
    <div>
      <div>{counter}</div>
      <button onClick={iterate}>Re-render</button>
    </div>
  )
}

export default MyComponent

钩子中的 React 生命周期方法

为了简单的视觉参考,请遵循此图片

在上图中你可以简单地看到,对于ComponentWillUnmount,你必须这样做

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

正如react document中所述:

You might be thinking that we’d need a separate effect to perform the cleanup. But code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up:

useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

所以我们唯一需要在钩子中使用 componentWillUnmount 的是 return useEffect 中的函数,如上所述。

鉴于

  • componentWillMount 已弃用 (1, 2, 3),建议的替换是执行 constructor
  • 中的代码
  • 在功能组件的 return 语句之前执行的代码在呈现之前隐式 运行
  • 挂载一个class组件的粗略计算ui价是功能组件的初始调用
  • 目标是在 ui 更新
  • 之前执行一次代码

解决方案是

运行 一个函数在函数组件的主体中只有一次。这可以通过 useStateuseMemouseEffect 来实现,具体取决于用例的时间要求uired。

由于代码需要 运行 在初始渲染提交到屏幕之前,这不符合 useEffect,因为“传递给 useEffect 的函数将在渲染完成后 运行致力于银幕。” 4.

因为我们要保证代码只会 运行 一次,这就取消了 useMemo 的资格,因为“在未来,React 可能会选择“忘记”一些以前记忆的值并重新计算它们在下一次渲染中” 5.

useState 支持 lazy initial state 计算,保证在初始渲染期间仅 运行 一次,这似乎是这项工作的不错选择。

useState 示例:

const runOnceBeforeRender = () => {};

const Component = () => {
  useState(runOnceBeforeRender);

  return (<></>);
}

作为自定义挂钩:

const runOnceBeforeRender = () => {};

const useOnInitialRender = (fn) => {
  useState(fn);
}

const Component = () => {
  useOnInitialRender(runOnceBeforeRender);

  return (<></>);
};

runOnceBeforeRender 函数可以选择 return 一个状态,该状态将在函数第一次呈现时立即可用,不会触发重新呈现。

一个(可能是不必要的)NPM 包:useOnce hook

所以对于 React hooks,我认为在 return 语句之前声明你的逻辑是可行的。您应该有一个默认设置为 true 的状态。在我的例子中,我调用状态 componentWillMount。然后条件为 运行 代码块当此状态为真时(代码块包含您要在 componentWillMount 中执行的逻辑),此块中的最后一条语句应该将 componentWillMountState 重置为 false(此步骤很重要,因为如果不这样做,将发生无限渲染) 例子

// do your imports here

const App = () =>  {
  useEffect(() => {
    console.log('component did mount')
  }, [])
  const [count, setCount] = useState(0);
  const [componentWillMount, setComponentWillMount] = useState(true);
  if (componentWillMount) {
    console.log('component will mount')
    // the logic you want in the componentWillMount lifecycle
    setComponentWillMount(false)
  }
  
  return (
    <div>
      <div>
      <button onClick={() => setCount(count + 1)}> 
        press me
      </button>
      <p>
        {count}
      </p>
      
      </div>
    </div>
  )
}

这可能不是 componentWillMount 方法的确切替代方法,但这里有一个方法可用于实现相同的效果 objective 但通过使用 useEffect :

首先将取数据的对象初始化为空值,定义useEffect方法:

const [details, setDetails] = useState("")

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

const retrieveData = () => {       
       getData()                  // get data from the server 
      .then(response => {
        console.log(response.data);
        setDetails(response.data)
      })
      .catch(e => {
        console.log(e);
      })
  }

现在在 JSX 中,我们 return 添加一个三元运算符

*return(
  <div>
    { 
   details ? (
   <div class="">
   <p>add Your Jsx Here</p>
</div>
): (
  <div>
<h4>Content is still Loading.....</h4>
</div>
)
}
  </div>
)*

这将确保在对象 'details' 中包含数据之前加载三元运算符的第二部分,这反过来会触发 useEffect 方法,该方法导致设置从服务器接收的数据 'details' 对象,因此渲染了主要的 JSX

简单地将依赖数组放在 React.useEffect() 中作为第二个参数。如果任何依赖项更新,挂钩将产生副作用,运行 并最终更新您的组件。

React 组件是一个函数吧?所以就让 componentWillMount 时刻成为 return 语句之前的函数体。

function componentWillMountMomentIsHere() {
  console.log('component will mount')
}


function AnyComponent(){
  const [greeting, setGreeting] = useState('Hello')

  componentWillMountMomentIsHere()

  
  return <h1>{greeting}</h1>
}

我们最近遇到了这个问题,因为我们需要在挂载组件时做一些事情,即我们需要更新全局状态。

所以我创建了这个钩子,不确定这种方法有多好,但到目前为止,只要我们有节制地使用它并且只用于简单的任务,它就可以工作。我可能不会将其用于网络请求和其他 long-running 以及复杂的任务。

import { useRef } from 'react';

function useComponentWillMount(callback: () => void) {
  const hasMounted = useRef(false);

  if (!hasMounted.current) {
    (() => {
      hasMounted.current = true;
      callback();
    })();
  }

  console.log(hasMounted.current);
  return null;
}

export default useComponentWillMount;