父组件setState后传入props更新子组件视图

问题场景

有一段时间没有写react了,最近开始在维护一个老的react项目,遇到了一个关于生命周期的问题,子组件代码如下

class SwitchDeviceTypeToName extends Component {
    constructor(props) {
        super(props);
        this.state = {
            deviceTypeState: "",
        }
    }

    componentDidMount = () => {
        this.updataState();
    }
    updataState = () => {
        const deviceTypeProps = this.props.deviceType;
        let deviceType = ifElseReturn(deviceTypeProps, {
            "1": <span value="1">电池</span>,
            "2": <span value="2">空调</span>,
        }, "无")

        this.setState({
            deviceTypeState: deviceType
        })
    }
    render() {
        const { deviceTypeState } = this.state;
        return (
            <span>
                {
                    deviceTypeState
                }
            </span>
        );
    }
}

场景:列表的数据需要轮询后台实时更新数据,在列表中修改了单条信息后,其他列的信息都正确更新并显示正常,唯独这个有一列没有正确更新,查看后发现原来这一列的数据中嵌套了一个子组件, 很明显子组件没有因为父组件的state更新而更新,但是明明父组件的数据是更新了的,但是父组件的更新并没有引起子组件本身重新渲染,想到了应该就是子组件生命周期的问题,继续去官网补课

官方小课堂

componentWillReceiveProps

定义:会在已挂载的组件接收新的 props 之前被调用,如果你需要更新状态以响应 prop 更改(例如,重置它),你可以比较 this.props 和 nextProps 并在此方法中使用 this.setState() 执行 state 转换。

    componentWillReceiveProps = nextProps => {
        this.updataState();
    }

这个方法可以解决这个问题,但是有个性能问题,因为如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法,频繁的更新子组件会造成一定的性能损失,所以一般情况下如果只想处理更改,请确保进行当前值与变更值的比较。不过这个地方是列表中其中一列的组件,所以无法同上一组数据的做对比,暂且这样解决。

派生 state 的引发的问题

派生state ,个人理解就是子组件中在父组件更新props时更新的state,这里要了解两个名词,受控和非受控,常用来指代表单的inputs,但是也可以用来描述数据频繁更新的组件,受控是因为数据是父组件传入的props的决定的,也可以理解为组件是被父组件直接控制的。但如果数据只保存在组件内部的state的话,外部无法控制,那就是非受控了

class EmailInput extends Component {
  state = { email: this.props.email };
  handleChange = event => {
    this.setState({ email: event.target.value });
  };
  componentWillReceiveProps(nextProps) {
    // 这会覆盖所有组件内的 state 更新!
    // 不要这样做。
    this.setState({ email: nextProps.email });
  }
    render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }

}

在这个子组件的例子中,不论在input中输入多少东西,只要父组件有更新,input的值会被立马初始化,因为调用了componentWillReceiveProps方法后直接取了传入的props,等于重新初始化一遍,但却没有对比条件。如果把更新前的数据和nextProps做对比后再操作setState,看似解决了这个问题,其实隐藏以一个问题,如果这个EmailInput组件被同一个两个公用,且两个表单输入EmailInput的值相同,这两个值在切换时是不会改变的,因为父组件传入的值是相等的,问题示例

解决的方法

官方推荐了两个解决的方法

一个是完全可控的组件

删除组件中state,可以在onChange中添加回调函数来在父组件保存输入的值,把EmailInput改造成一个轻量的函数组件:

function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}

有 key 的非可控组件

key 是个特殊的 React 属性,当 key 变化时, React 会创建一个新的而不是更新一个既有的组件。Keys 一般用来渲染动态列表,但是这里也可以使用,如下:

<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.15.5