useState 不会更新组件重新渲染的值

时间:2021-01-31 12:56:08

标签: reactjs react-hooks

在以下示例中,单击名称更改按钮会更改状态,但不会在 UI 上呈现。

正确的预期结果是将名称更改为“Mike”

这里是SandBox

import React, { useState } from "react";
import ReactDOM from "react-dom";

import "./styles.css";


const useForm = initialValues => {
  const [values, setValues] = useState(initialValues);

  return [
    values,
    e => {
      console.log(e.target.value);
      setValues({
        ...values,
        [e.target.name]: e.target.value
      });
    }
  ]
}

let count = 0;

const Hello = (props) => {
  console.log(`Hello.props: ${props.name} `)
  const [values, handleChange] = useForm({ nameInput: props.name });
  count++
  console.log(`RenderedAmount: ${values.nameInput} : ${count}`)
  return (
  <div>
    Name: <input value={values.nameInput} onChange={handleChange} name="nameInput" /><br/>
    Hello {values.nameInput}!
  </div>
);
}

const Container = (props) => {
  const [ name, setName] = useState(props.name);
  console.log(`Container: Name is ${name}`)
  return(
    <div>
      <Hello name={name}/>
      <button onClick={()=>{setName("Mike")}}>
        Change Name
      </button>
    </div>
  )
}


const rootElement = document.getElementById("root");
ReactDOM.render(<Container name="John"/>, rootElement);

5 个答案:

答案 0 :(得分:2)

为了让它按您想要的方式工作,您需要了解以下几点:

  • 出于优化目的,React 内部使用了一些技术来最小化更新 UI 所需的 DOM 操作数量,这意味着如果您再次将状态设置为旧值,React 将退出 shouldComponentUpdate,并重新渲染不会发生。因此,如果您始终将容器组件中的状态设置为 "Mike",则只会导致组件重新渲染一次,直到您设置不同的值。

  • 当使用 useState 时,我们只能通过在函数调用时将值作为参数传递给它一次来初始化它。所以每当组件重新渲染时,props.name 不会在 useState 函数调用中设置。

    const [values, handleChange] = useForm({ nameInput: props.name });  // this only sets the initial value for the first time 
    

    通常在这种情况下,您需要使用 useEffect 钩子(相当于 componentDidMount)并在其中设置您的状态。

  • 目前您的 useForm 钩子仅通过 DOM 事件更新值,如果我们想在 DOM 事件之外更新状态,这会变得很困难。例如,如果我们最终根据从 prop 获得的值更新 useEffect 中的 useForm 状态,那么我们需要能够指定要更新的字段。

我相信您拥有的此设置仅用于测试,但根据我上面的解释,以下是您可以执行的操作以使其正常工作。

更新了您的沙箱: https://codesandbox.io/s/react-hooks-usestate-forked-xk3bm

const useForm = (initialValues) => {
  const [values, setValues] = useState(initialValues);

  return [
    values,
    (newValue) => {
      setValues({
        ...values,
        ...newValue
      });
    }
  ];
};

let count = 0;

const Hello = (props) => {
  const [values, handleChange] = useForm({ "nameInput": props.name });
  useEffect(() => {
    handleChange({ "nameInput": props.name});
    // add props.name as a dependency 
    // so only update the nameInput if its value is changed
  }, [props.name])
  return (
    <div>
      Name:{" "}
      <input
        value={values.nameInput}
        onChange={({target}) => handleChange({"nameInput" : target.value})}
        name="nameInput"
      />
      <br />
      Hello {values.nameInput}!
    </div>
  );
};

const Container = (props) => {
  const [name, setName] = useState(props.name);
  return (
    <div>
      <Hello name={name} />
      <button
        onClick={() => {
          count++; // I'm incrementing the counter here to fool react to update the state with a new value. You will fix this however you want
          setName("Mike" + count);
        }}
      >
        Change Name To Mike #
      </button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Container name="John" />, rootElement);

答案 1 :(得分:1)

@Spring 的回答更好

我想出了一个非常作弊的解决方案:

// from the Hello component
const [values, handleChange] = useForm({ nameInput: props.name });
if (values.nameInput !== props.name) {
  handleChange({ target: { name: "nameInput", value: props.name } });
}
count++;
...

我基本上只是检查返回的状态是否过时并将其设置为新值。这个答案不应该像这样实现,但我认为你可以用它找到更好的解决方案。

答案 2 :(得分:0)

首先在您的 cutom 钩子中添加一个函数,该函数根据直接传递的值而不是输入事件作为第三个参数来改变状态,使用

这是一个有效的 CodePen

自定义钩子应该看起来

const useForm = (initialValues) => {
  const [values, setValues] = useState(initialValues);

  return [
    values,
    (e) => {
      console.log(e.target.value);
      setValues({
        ...values,
        [e.target.name]: e.target.value
      });
    },
    (vals) => {
      setValues({
        ...values,
        ...vals
      });
    }
  ];
};

useEffect 在你的 hello 中挂钩以观察 props.name 变化,并使用该 cutom 挂钩第三个函数在 state 中传播新值, 还要注意,它应该与发送的初始值完全相同( useform );

你好,最后添加这个:

const Hello = (props) => {
   console.log(`Hello.props: ${props.name} `);
  //  hook with three param 
  const [values, handleChange, setValue] = useForm({ nameInput: props.name });
  // watch props.name change !
  useEffect(() => {
    setValue({ nameInput: props.name });
  }, [props.name]);
  .
  .
  .
  .
  .
}

答案 3 :(得分:0)

您需要在 Hello 函数中使用 props.name 而不是 values.nameInput

状态更改以再次渲染组件。组件的属性在您的情况下发生了变化,因此您需要使用“prop”

代码:https://codesandbox.io/s/react-hooks-usestate-forked-itk2r?file=/src/index.js:658-668

答案 4 :(得分:-3)

这是因为 React hooks 只能在函数式 React 组件的开头调用,也就是说,返回 HTML (JSX) 且名称以大写字母开头的 JS 函数。在这种情况下,您正在对不是 React 功能组件的函数调用 useState(第 8 行)