如何使用 React hooks 将道具同步到状态:setState()

How to sync props to state using React hooks : setState()

我正在尝试使用 React hook setState() 使用组件接收的道具来设置状态。我试过使用以下代码:

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

const Persons = (props) =>  {

    // console.log(props.name);

   const [nameState , setNameState] = useState(props)

   console.log(nameState.name);
   console.log(props.name);

   return (
            <div>
                <p>My name is {props.name} and my age is {props.age}</p>
                <p>My profession is {props.profession}</p>
            </div>
        )

}

export default Persons;

问题是状态是在加载组件时设置的。但是当它收到新道具时,状态不会更新。在这种情况下如何更新状态?提前致谢。

为此,您需要使用 useEffect 让您的代码看起来像。 如果专业人士没有改变,你想避免再次重新渲染,那么你必须先检查 useEffect,然后将道具设置为当前变量。

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

const Persons = props => {
  // console.log(props.name);

  const [nameState, setNameState] = useState(props);

  console.log(nameState.name);
  console.log(props.name);
  useEffect(
    () => {
      if (nameState !== props.name) {
        setNameState(props.name);
      }
    },
    [nameState]
  );
  return (
    <div>
      <p>
        My name is {props.name} and my age is {props.age}
      </p>
      <p>My profession is {props.profession}</p>
    </div>
  );
};

export default Persons;

Demo

useState hooks 函数参数只被使用一次,而不是每次 prop 改变时。您必须使用 useEffect 挂钩来实现您所谓的 componentWillReceiveProps/getDerivedStateFromProps 功能

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

const Persons = (props) =>  {
   const [nameState , setNameState] = useState(props)

   useEffect(() => {
       setNameState(props);
   }, [props])

   return (
            <div>
                <p>My name is {props.name} and my age is {props.age}</p>
                <p>My profession is {props.profession}</p>
            </div>
        )

}

export default Persons;
import React, { useState, useEffect } from "react";

const Persons = props => {
  // console.log(props.name);

  const [nameState, setNameState] = useState(props);

  console.log(nameState.name);
  console.log(props.name);
  useEffect(
    () => {
      if (nameState !== props) {
        setNameState(props);
      }
    },
    [nameState]
  );
  return (
    <div>
      <p>
        My name is {props.name} and my age is {props.age}
      </p>
      <p>My profession is {props.profession}</p>
    </div>
  );
};

export default Persons;

根据 Hooks react 文档,只要有任何 props 更新或组件中有任何更新,就会调用 useEffect。所以你需要在更新 useState 之前检查条件,然后更新你的值,这样它就不会不断地重新渲染

这个大体思路可以放到hook中:

export function useStateFromProp(initialValue) {
  const [value, setValue] = useState(initialValue);

  useEffect(() => setValue(initialValue), [initialValue]);

  return [value, setValue];
}


function MyComponent({ value: initialValue }) {
  const [value, setValue] = useStateFromProp(initialValue);

  return (...);
}

useState(props) 中的 props 值仅被使用 during the initial render,进一步的状态更新由 setter setNameState.

完成

另外,更新derived state时不需要useEffect:

const Person = props => {
  const [nameState, setNameState] = useState(props.name);
  // update derived state conditionally without useEffect
  if (props.name !== nameState) setNameState(props.name);
  // ... other render code
};

来自 React docs:

[...] you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.

[...] an update during rendering is exactly what getDerivedStateFromProps has always been like conceptually.

本质上,我们可以通过摆脱额外的浏览器重绘阶段来优化性能,因为 useEffect 始终在渲染提交到屏幕后运行。

工作示例

这是一个说明上述模式的人为示例 - 在实际代码中,您可以直接阅读 props.name。有关更合适的派生状态用例,请参阅 React blog post

const Person = props => {
  const [nameState, setNameState] = React.useState(props.name);
  // Here, we update derived state without useEffect
  if (props.name !== nameState) setNameState(props.name);

  return (
    <p>
      <h3>Person</h3>
      <div>{nameState} (from derived state)</div>
      <div>{props.name} (from props)</div>
      <p>Note: Derived state is synchronized/contains same value as props.name</p>
    </p>
  );
};

const App = () => {
  const [personName, setPersonName] = React.useState("Lui");
  const changeName = () => setPersonName(personName === "Lukas" ? "Lui" : "Lukas");

  return (
    <div>
      <Person name={personName} />
      <button onClick={changeName}>Change props</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

我认为问题表明试图使用一个概念变量或一组变量来做两件不同的事情。例如试图让 props.namename 做同样的事情。

所以如果

const [name, setName] = useState(props.name)

还不够,您会发现自己试图在稍后的函数中将 props.name 强制为状态变量 name,然后可能 name 正在超载。尝试设置另一个状态变量 - 例如。 updatedName 看看效果是否更好。

原始示例未演示此问题,因为除了在日志语句中从未使用过状态变量。

如果 const [name, setName] = useState(props.name) 在 re-render 上更新,那么状态变量 name 就没有意义,因为它总是与 props.name 相同(以及进一步的尝试更改它会导致 re-render).

我想出了一个避免 useEffect 的替代解决方案。相反,它使用两个 useState。我为您将其放入自定义挂钩中:

export function useStateFromProp(propValue) {
  const [value,          setValue         ] = useState(propValue);
  const [propValueState, setPropValueState] = useState(propValue);

  if (propValueState != propValue) {
    setPropValueState(propValue);
    setValue(propValue);
  }

  return [value, setValue];
}


function MyComponent({ value: propValue }) {
  const [value, setValue] = useStateFromProp(propValue);

  return (...);
}

主要好处是现在通常由 useEffect 触发的重新渲染发生在重新渲染任何子组件之前。这样会更快。

免责声明:我还没有对此进行测试。我做了一些谷歌搜索,找到了这篇支持文章:https://pretagteam.com/question/in-react-hooks-when-calling-setstate-directly-during-render-is-the-rerender-guaranteed-to-run-before-the-render-of-children

如果您需要根据 props 和其他状态计算状态 无需 额外 重新渲染 ,请考虑:

a) 使用 useMemo 钩子。

const Component = ({ name }) => {
  const [surname, setSurname] = useState('');

  const fullName = useMemo(() => {
     return name + ' ' + surname;
  }, [name, surname])

  ...
}

b) 如果渲染不是很重,则计算内部渲染:

const Component = ({ name }) => {
  const [surname, setSurname] = useState('');

  const fullName = name + ' ' + surname;

  ...
}

c) 对于你需要比较之前的道具并且应该能够从另一个地方更新状态的困难情况,你可以使用参考文献,虽然看起来不太好:

const Component = ({ name }) => {
  const prevNameRef = useRef()
  const derivedState = useRef();

  if (prevNameRef.current !== name) {
    derivedState.current = ...
    prevNameRef.current = name;
  }
 
  // some other place
  derivedState.current = ...
}