ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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


N
NearHuscarl

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

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

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

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

对其他生命周期事件的很好解释,但这并不是关于 componentWillMount() 替代方案的具体问题。
在我看到的 PanResponder 示例中,componentWillMount 似乎是必需的,否则你会得到未定义的 panHandlers。
我现在真正理解了 useEffect() 功能,谢谢。
为什么这是一个公认的答案?您没有提到 componentWillMount 的钩子等效项
@techexpert 该问题要求等效于 componentWillMount,而不是 componentWillUnmount。这个答案确实根本没有回答这个问题,只是重申了 OP 已经暗示要知道的内容。
B
Ben Carp

使用ComponentWillMount 挂钩

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

    if (willMount.current) cb()

    willMount.current = false
}

当存在顺序问题(例如在另一个脚本之前运行)时,这个钩子可能是一个保护程序。如果不是这种情况,请使用更符合 React hooks 范例的 useComnponentDidMount。

useComponentDidMount 挂钩

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

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

使用效果范式

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

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

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

  ...
}

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

组件将挂载讨论

在类组件中,componentWillMount 被视为旧版(source 1source2)。它是遗留的,因为它可能会运行不止一次,并且还有另一种选择 - 使用构造函数。这些考虑与功能组件无关。


这是回答问题并且有意义的唯一答案。谢谢!
唯一的问题是由于涉及状态更新,您会获得额外的渲染。通过使用 ref,您无需额外渲染即可获得所需的行为:` const useComponentWillMount = func => { const willMount = useRef(true); useEffect(() => { willMount.current = false; }, []); if (willMount.current) { func(); } }; `
这种基于 useEffectcomponentWillMount 功能实现有两个问题。第一个是功能组件中没有挂载生命周期,两个钩子都会在组件渲染后运行,因此 Runs only once before component mounts 具有误导性。第二个是在服务器渲染时调用 componentWillMount 而不是 useEffect。许多库仍然依赖 UNSAFE_componentWillMount,因为目前它是触发副作用服务器端的唯一方法。
@PaoloMoretti,谢谢。这个 componentWillMount 钩子并不完全等同于类组件中的 componentWillMount 生命周期。但是,传递给它的函数将立即运行,仅在第一次调用时运行。这实际上意味着它将在渲染之前运行,甚至在它第一次返回值之前运行。我们可以同意吗?我同意使用名称 componentWillMount 并不理想,因为该名称具有类生命周期版本的某些含义。也许我最好称它为“useRunPreMount”。
对不起,我实际上没有意识到 useComponentWillMount 回调在第一次调用时运行。在那种情况下,我所说的第一个问题是错误的,我认为它可能是最接近 componentWillMount 的带有钩子的实现。第二个问题仍然有效,因为 useEffect 不在 SSR 中运行,所以我认为它会始终执行回调,因为 willMount.current 不会被 useEffect 设置为 false
M
MING WU

根据 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')
}

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

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

2021 年 7 月 9 日更新:

关于依赖的一些更新:

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

useCallback 能够记住一个函数。 useMemo 能够记住一个对象。

见这篇文章:

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


问题是如何用钩子实现它
但是您不需要使用钩子来实现它,因为它不受支持。无需学习如何使用钩子来做到这一点。
既然您已经提到 componentDidMount 是要使用的正确生命周期,您可以在答案中添加如何实现它,然后您的答案将比接受的答案更有意义
当然,这应该是公认的答案 - 它解释了 ComponentWillMount 在钩子范例中不可用。功能组件的初始化被简化 - 它只需要成为功能的一部分
这与 componentWillMount 有何相同之处?如果您将代码投入到功能组件中,那么它将运行每个渲染,而不仅仅是在组件即将挂载时。
j
jpmarks

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

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

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

这是有效的,因为 useMemo 不需要实际返回一个值,并且您不必实际将它用作任何东西,但是因为它基于依赖项记住一个值,该依赖项只会运行一次(“[]”)并且它位于我们的组件之上当组件在其他任何东西之前安装时运行一次。


这可能适用于当前的实现,但 React 文档明确表示不要这样做。 您可以依赖 useMemo 作为性能优化,而不是语义保证。 reactjs.org/docs/hooks-reference.html#usememo
我同意。这有点冒险,但如果你小心使用它,你可以得到你想要的。我发现它对于初始化独立于组件渲染状态的事物很有用。
l
lokender singh

钩子中的 React 生命周期方法

对于简单的视觉参考,请按照此图片

https://i.stack.imgur.com/7q1jC.jpg

正如您在上图中简单看到的那样,对于 ComponentWillUnmount,您必须这样做

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

谢谢!你能把原始文章/帖子的链接放在这里吗?图片质量很差 [картинка шакального качества]。
问题是针对 componentWillMount,而不是 componentWillUnmount
J
Jiri Mihal

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


我没有深入研究您的示例,但您的第一个代码片段对我有用,谢谢!
精确而简单。
抱歉,您如何保证“if”中的内容在 func 组件挂载到 dom 之前运行?
r
rob2d

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

例如:


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

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

使用初始化器/设置器或 useEffectuseState 的好处是虽然它可以计算渲染通道,但用户不会注意到对 DOM 的实际重新渲染,并且它在 before 第一个明显的渲染,useEffect 不是这种情况。缺点当然是您的第一次渲染略有延迟,因为在绘制到屏幕之前必须进行检查/更新。不过,这确实取决于您的用例。

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


useLayoutEffect 是要走的路!!!!这回答了我关于检查用户是否登录的问题。(问题是,组件会加载,然后检查用户是否登录。)我的问题是,这是标准做法吗?我没有在太多地方看到
是的,这很常见;在官方 React 文档中也提到过——只是在较小的文本中,因为双 DOM 渲染的后果是为了在用户注意到之前运行逻辑。
实际上它在渲染组件后运行。所以它与componentWillMount完全不同。
正如@JiriMihal 提到的,useLayoutEffect 在渲染后运行,来自 React Docs (reactjs.org/docs/hooks-reference.html#uselayouteffect):If you’re migrating code from a class component, note useLayoutEffect fires in the same phase as componentDidMount and componentDidUpdate.
L
Littletime

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

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

由于状态改变,这将导致重新渲染
m
martinstark

鉴于

componentWillMount 已弃用 (1, 2, 3),建议的替换是在构造函数中执行代码

在功能组件的 return 语句之前执行的代码在渲染之前隐式运行

挂载类组件的粗略等价物是功能组件的初始调用

目标是在更新 ui 之前执行一次代码

解决方案是

在功能组件的主体中只运行一次功能。这可以通过 useStateuseMemouseEffect 来实现,具体取决于用例所需的时间。

由于代码需要在初始渲染提交到屏幕之前运行,因此 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 函数可以选择返回一个状态,该状态将在函数第一次渲染时立即可用,不会触发重新渲染。

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


佚名

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

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


usememo 用于性能优化。如果一个 prop 发生变化,一个钩子会在已经挂载后再次渲染,这违背了作者的目的。
钩子不会渲染,钩子会导致组件重新渲染。 Hooks 也不依赖于 props,它们只是使用副作用管理状态和组件生命周期事件的工具。
A
AfikDeri

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

根据文档,useEffect 可以返回“清理”函数。此函数不会在第一次 useEffect 调用时调用,只会在后续调用中调用。

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

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

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

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

希望这可以帮助。


如果它与 componentWillMount 无关,这有什么帮助?我错过了什么吗?
是的,您错过了这样一个事实:在同一个 useEffect 调用中,您以一种简洁明了的方式获得了 componentWillMountcomponentWillUnmount 的相同功能
这不是真的,useEffect 仅在渲染之后运行,而 componentWillMount 在组件渲染之前运行。
@Overcode 我说的是 componentDidMount 而不是 componentWillMount。我错过了这个问题,我的错。
K
Kerem atam

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

f
ford04

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

componentWillMountdeprecated and considered legacy。反应recommendation

通常,我们建议使用 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; };

d
dimyG

大多数人可能很清楚,但请记住,在函数组件的主体内部调用的函数充当 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

S
Saba

正如 react document 中所述:

您可能会认为我们需要一个单独的效果来执行清理。但是用于添加和删除订阅的代码是如此紧密相关,以至于 useEffect 旨在将其保持在一起。如果你的效果返回一个函数,React 会在需要清理的时候运行它:

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';
}

所以我们唯一需要在 hooks 中使用 componentWillUnmount 就是在 useEffect 中返回一个函数,如上所述。


N
Nature

所以对于 React 钩子,我认为在 return 语句可以工作之前声明你的逻辑。您应该有一个默认设置为 true 的状态。在我的例子中,我调用了状态组件WillMount。然后有条件在此状态为 true 时运行代码块(代码块包含您要在 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>
  )
}

P
PK missfartypants

这可能不是 componentWillMount 方法的确切替代方法,但这是一种可用于实现相同目标但使用 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(
  <div>
    { 
   details ? (
   <div class="">
   <p>add Your Jsx Here</p>
</div>
): (
  <div>
<h4>Content is still Loading.....</h4>
</div>
)
}
  </div>
)*

这将确保在对象“详细信息”中有数据之前,加载三元运算符的第二部分,这反过来会触发 useEffect 方法,该方法导致在“详细信息”对象中设置从服务器接收的数据,因此呈现主要的 JSX


S
Shah

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


a
aprilmintacpineda

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

所以我创建了这个钩子,不确定这是一种多么好的方法,但到目前为止,只要我们谨慎使用它并且只用于简单的任务,它就可以工作。我可能不会将它用于网络请求和其他长时间运行的复杂任务。

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;

B
Bayramali Başgül

React 组件是一个函数,对吗?因此,只需让 componentWillMount 时刻成为 return 语句之前的函数体。

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


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

  componentWillMountMomentIsHere()

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

佚名

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

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

这不是一个答案。问题是关于如何将 componentWillMount 与 Hooks 一起使用。