如何使用 React Hook 将道具同步到状态: 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;

问题是在加载组件时设置了状态。但是,当它收到新的道具,国家没有得到更新。在这种情况下如何更新状态?先谢谢你。

89314 次浏览

为此,您需要使用 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;

演示

useState挂钩函数参数只使用一次,并不是每次道具变化时都使用。您必须使用 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;

根据 Hook 的反应文档,当任何道具被更新或组件中有任何更新时,那么 useEffect 将被调用。因此,您需要在更新 useState 之前检查条件,然后更新您的值,这样它就不会不断地重新呈现

这个概念可以放进钩子里:

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 (...);
}

The props value in useState(props) is used only 在初始渲染过程中, further state updates are done with the setter setNameState.

此外,更新 派生态,派生态时不需要 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
};

来自 反应文件:

[ ... ]你可以 更新状态在渲染过程中。反应将 再跑的组件与更新状态立即 在退出第一次渲染之后,所以它不会是昂贵的。

[ ... ]渲染过程中的更新正是 getDerivedStateFromProps在概念上的一贯做法。

实际上,我们可以通过取消额外的浏览器重新绘制阶段来优化性能,因为 useEffect总是在渲染提交到屏幕之后运行。

举个例子

This is a contrived example illustrating above pattern - in real code you would read props.name directly. See the React blog post for more appropriate derived state use cases.

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)

isn't enough and you find yourself trying to force props.name into state variable name later in the function then maybe name is being overloaded. Try setting up another state variable - eg. updatedName and see if things work better.

最初的示例没有演示这个问题,因为除了在日志语句中,状态变量从不使用。

如果 const [name, setName] = useState(props.name)在重新呈现时更新,那么状态变量 name就没有意义了,因为它始终与 props.name相同(进一步尝试更改它将导致重新呈现)。

我想到了一个避免 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

如果你需要计算状态从道具和其他状态 没有额外的 重新渲染,考虑:

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)对于那些你需要 比较一下之前的道具并且应该能够 update state from another place的困难情况,你可以使用参考文献,尽管它看起来不是很好:

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


if (prevNameRef.current !== name) {
derivedState.current = ...
prevNameRef.current = name;
}
 

// some other place
derivedState.current = ...
}