避免将 props 的值复制给 state!这是一个常见的错误
通过下文,以了解出现 state 依赖 props 的情况该如何处理。
什么是派送state
个人理解:组件将接收到的props属性作为state的数据
eg:
import React, { Component } from 'react'
//子组件
class Child extends Component {
constructor(props) {
super(props)
this.state = {
title: this.props.title //将接收到的props的属性作为state
}
}
render() {
return (
<div>
{this.state.title}
</div>
)
}
}
//父组件
export default class Parent extends Component {
render() {
return (
<div>
<Child title="我是props派生的数据"></Child> //1)title是组件Parent传递给子组件Child的属性
</div>
)
}
}
什么时候使用派生state
- 第一种情况:直接复制 props 到 state 上。
this.state = {
title: this.props.title //将接收到的props的属性作为state
}
- 第二种情况:如果 props 和 state 不一致就更新 state
componentWillReceiveProps(nextProps) {
if(nextProps.title!==this.props.title){
this.setState({ value: nextProps.title});
}
}
第一种情况:直接复制 props 到 state 上
最常见的误解就是 getDerivedStateFromProps 和 componentWillReceiveProps 只会在 props “改变”时才会调用。实际上只要父级重新渲染时,这两个生命周期函数就会重新调用,不管 props 有没有“变化”.
总结:props变化跟父组件重新渲染(不管 props 有没有“变化”), getDerivedStateFromProps 和 componentWillReceiveProps 都会重新执行)。
造成的问题:父组件重新渲染,子组件中我们输入的所有东西都会丢失。
官方例子:
import React, { Fragment, Component } from "react";
import { render } from "react-dom";
//子组件
class EmailInput extends Component {
state = {
email: this.props.email
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
handleChange = event => {
this.setState({ email: event.target.value });
};
componentWillReceiveProps(nextProps) { //父组件更新会执行这个方法,让子组件通过handleChange 方法保存的Email 丢失
this.setState({ email: nextProps.email });
}
}
//父组件,通过setInterval实现一秒更新一次父组件
class Timer extends Component {
state = {
count: 0
};
componentDidMount() {
this.interval = setInterval(
() =>
this.setState(prevState => ({
count: prevState.count + 1
})),
1000
);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<Fragment>
<EmailInput email="example@google.com" />
</Fragment>
);
}
}
render(<Timer />, document.getElementById("root"));
第二种情况:在props变化之后更新state
这里出现的问题是:index.js的数据中,id=1跟id=2列表项中的Email是一样的,修改了id=1列表项中的Email值,之后切换到id=2,但是Email 并没有恢复成初始值,原因是传给UncontrolledEmaillnput.js的email没有发生变化。
组件1:index.js
const fakeAccounts = [
{
id: 1,
name: "One",
email: "fake.email@example.com",
password: "totally fake"
},
{
id: 2,
name: "Two",
email: "fake.email@example.com",
password: "also fake"
},
{
id: 3,
name: "Three",
email: "also.fake.email@example.com",
password: "definitely fake"
}
];
render(
<AccountsList accounts={fakeAccounts} />,
document.getElementById("root")
);
组件2:AccountsList.js
export default class AccountsList extends Component {
state = {
selectedIndex: 0
};
render() {
const { accounts } = this.props;
const { selectedIndex } = this.state;
return (
<Fragment>
<EditAccountForm account={accounts[selectedIndex]} /> //当前选择列表项的数据
<p>
Accounts:
{this.props.accounts.map((account, index) => ( //渲染列表数据
<label key={account.id}>
<input
type="radio"
name="account"
checked={selectedIndex === index}
onChange={() => this.setState({ selectedIndex: index })} //切换列表,重新渲染,将新的列表项数据传递给组件3
/>{" "}
{account.name}
</label>
))}
</p>
</Fragment>
);
}
}
组件3:EditAccountForm.js
export default class EditAccountForm extends Component {
render() {
const { account } = this.props;
return (
<form>
<h2>Account "{account.name}"</h2>
<UncontrolledEmailInput email={account.email} /> //将列表项的email传递给组件4
</form>
);
}
}
组件4:UncontrolledEmailInput.js
export default class UncontrolledEmailInput extends Component {
state = {
email: this.props.email
};
//判断接收到的Email是否有变化,有变化的话重置Email初始值。修改了第一个列表项的Email之后,切换到第二个列表项,Email没有发生变化,原因是传入的props.email 一样,下面的代码并没有执行
componentWillReceiveProps(nextProps) {
if (nextProps.email !== this.props.email) {
this.setState({ email: nextProps.email }); //代码1
}
}
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return (
<label>
Email: <input onChange={this.handleChange} value={this.state.email} />
</label>
);
}
}
解决办法(任何数据,都要保证只有一个数据来源,而且避免直接复制它)
1.完全可控的组件
将子组件中的state删除,使用父组件传递的props数据控制子组件状态的改变,让子组件完全受控于父组件,可以将class子组件转换成 function函数组件。
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}
2.有 key 的非可控组件
state接收父组件props传递的数据,但是数据的改变不受父组件的控制。
1)可以使用 key 这个特殊的 React 属性。当 key 变化时, React 会创建一个新的而不是更新一个既有的组件.
2)这听起来很慢,但是这点的性能是可以忽略的。如果在组件树的更新上有很重的逻辑,这样反而会更快,因为省略了子组件 diff。
class EmailInput extends Component {
state = { email: this.props.defaultEmail };
handleChange = event => {
this.setState({ email: event.target.value });
};
render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}
3.添加key失效的解决办法
如果某些情况下 key 不起作用(可能是组件初始化的开销太大)
解决办法是:可以在渲染的时候,给组件一个唯一的属性,例如:id,title,msg,err等都可以,只要能区分开就好。
static getDerivedStateFromProps(props, state) {
if (props.uID !== state.uID) {
return {
uID: props.uID,
email: props.defaultEmail
};
}
return null;
}
}
4.使用ref来重置组件
refs 在某些情况下很有用,比如这个。但通常我们建议谨慎使用。即使是做一个演示,这个命令式的方法也是非理想的,因为这会导致两次而不是一次渲染。
eg:
handleChange = index => {
this.setState({ selectedIndex: index }, () => {
const selectedAccount = this.props.accounts[index];
this.inputRef.current.resetEmailForNewUser(selectedAccount.email); //resetEmailForNewUser是子组件的方法
});
};
<UncontrolledEmailInput defaultEmail={selectedAccount.email} ref={this.inputRef} />