如何在函数组件中使用构造函数(箭头函数语法)?

40

考虑到这个组件:

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

const NewGoalInput = props => {
  return (
    <input type="text" onKeyUp={handleKeyUp}/>
  )
}

const handleKeyUp = (e) => {
  if (e.key === "Enter") {
    // TODO Add goal
  }
}

export default NewGoalInput

我该如何添加一个构造函数,以便在不使用extends React.Component语法的情况下定义状态?


2
函数式组件没有实现生命周期工作流程,尽管它们没有状态。如果您想使用状态,则必须从 React.Component 进行扩展。 - Ematipico
8个回答

37

因为它是一个无状态组件,所以它没有组件生命周期。因此您无法指定constructor

您需要扩展React.Component来创建一个有状态组件,然后需要一个构造函数,您将能够使用state

更新React 16.8.0和引入钩子之后,有更多的选择。

钩子是一项新的功能提议,它允许您在不编写类的情况下使用状态和其他React功能。它们作为v16.8.0的一部分发布在React中。

无状态:

import React from "react"

const Stateless = ({name}) => (
  <div>{`Hi ${name}`}</div>
);

有状态的:

可以访问组件生命周期方法和本地状态。

class Stateful extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    const { count } = this.state;
    document.title = `You've clicked ${count} times.`;
  }

  componentDidUpdate() {
    const { count } = this.state;
    document.title = `You've clicked ${count} times.`;
  }

  render() {
    const { count } = this.state;
    return (
      <div>
        <p>You've clicked {count} times.</p>
        <button onClick={() => this.setState({ count: count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

使用Hooks:

能够使用State HookEffect Hook

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

import React, { useState, useEffect } from "react";

const UsingHooks = () => {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You've clicked ${count} times.`;
  });

  return (
    // <> is a short syntax for <React.Fragment> and can be used instead of a wrapping div
    <>
      <p>You've clicked {count} times.</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </>
  );
}

2
非常好的解释。然而,这并不能完全回答问题,因为constructor并不是生命周期方法的一部分,这意味着useEffect钩子在函数组件中无法帮助创建constructor - Olumide

15

现在我们有了useState和hooks,答案有点过时。我遇到这个问题是因为我做错了什么。这里是我正在做的一些简化代码。

// set an initial state
const [ value, setValue ] = useState(0)

// gets called after component is re-rendered
useEffect(() => {
   // callback to parent that set props
   props.update()
})

// if we have an existing value passed in
if (props.value) {
   setValue(props.value)
}
这段代码是使用hooks从一个有状态类转变为函数,最初在构造函数中设置默认props,但是函数没有构造函数,因此每次组件重新渲染时都会发生检查:
  1. 调用 useState
  2. 触发重新渲染
  3. 触发 useEffect
  4. 调用父级并设置 props
  5. props更新导致子组件再次渲染
  6. 返回第1步
如您所见,这会导致无限循环。解决方案真的很简单。以下是从原始版本的模拟差异。
- const [ value, setValue ] = useState(0)
+ const [ value, setValue ] = useState(props.value || 0)

- if (props.value) {
-   setValue(props.value)
- }

基本上,只需从props初始化状态,并且不要做傻事,比如在没有事件或回调的情况下调用useState


7
您可以使用以下方式使用useMemo钩子作为函数式组件的构造函数。有人建议使用useEffect,但它将在渲染后调用。
useMemo(() => {
  console.log('This is useMemo')
}, []);

3

您可以在函数组件的第一行设置一个useState,并将一个函数作为“初始值”:

const MyComponentName = props => {
  useState(() => {
    console.log('this will run the first time the component renders!');
  });
  return <div>my component!</div>;
};

如果您需要从“构造函数”向组件公开任何值,请记住从此函数返回的内容是由useState返回的数组中的第一项,如下所示:const [value] = useState(() => { /* ... */ return '123'; }) - Gustavo Straube

2

对于那些想要在组件挂载之前运行一次函数的人,这里有一个钩子(使用 TypeScript 编写)。

通常情况下,useEffectuseLayoutEffect 足够了,但它们在组件挂载后运行,有时您希望在此之前运行代码(如构造函数)。

import React, { useRef } from "react";

function useOnce<Type>(callBack: () => Type): Type {
  const result = useRef<Type | null>(null);

  if (result.current !== null) {
    return result.current;
  }

  result.current = callBack();
  return result.current;
}

const Component: React.FC<{}> = () => {
  const result = useOnce(() => {/* Code you would normally put in a constructor */});

  return <div />
}

2

不需要。你示例中的组件类型称为“无状态函数式组件”。它没有状态和生命周期方法。如果你想让你的组件有状态,你将需要编写一个类组件。


3
现代版的React可以使用钩子(hooks)在函数式组件中管理状态(state),这使得您的答案过时了。 - Olumide

1
使用 useEffect 模拟函数组件中的构造函数。
useEffect(() => {
  ... here your init code
}, []);

这就是它!很简单!这个 useEffect 只在组件加载时运行一次,永远不会再次运行,只需不要忘记在结尾添加方括号即可。

谢谢!学到了新知识。 - Kenny Meyer
7
useEffect钩子在渲染函数之后运行,因此即使您传递第二个参数("方括号"),它们也不具有与构造函数相同的功能。类组件中的构造函数甚至在第一次渲染调用之前运行。 - Sasha Kondrashov
2
Timofey是正确的。useEffect()函数确实模拟了componentDidMount生命周期方法,但它并不像构造函数一样。你不能使用它来设置应该在后面呈现的初始数据,就像在基于类的构造函数中完成的那样。 - mogio
它不是构造函数的替代,而是替代componentDidMount。 - raja pateriya

0

或者,您可以使用react-afc

import { afc, reactive } from 'react-afc'

function heavyCalc() {/*...*/}

const Conponent = afc(props => {
  // Called once, before the first render

  const state = reactive({
    name: 'Stack',
    inputsCount: 0
  })

  // Without useMemo(..., [])
  const result = heavyCalc()

  // The function is created once and does not cause
  // a re-render of child components
  function onInput(e) {
    state.inputsCount++
    state.name = e.currentTarget.value
  }

  // Saved between renders (no longer need useRef)
  let rendersCount = 0

  // Must return the render-function
  return () => {
    // The function works like a regular react-component
    // Here you can use the usual hooks
    rendersCount++
    return (
      <input onChange={onInput} value={state.name}/>
    )
  }
})

该包含有必要的方法,用于处理 状态(包括 redux)、react-hooks生命周期方法和 上下文


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接