React getDerivedStateFromProps使用异常问题及解决方案咨询
你遇到的问题很典型——当点击Counter组件的+/-按钮更新本地state时,getDerivedStateFromProps会被触发并把count值重置为父组件传过来的props值,而旧的UNSAFE_componentWillReceiveProps版本却没有这个问题。我来帮你分析原因并给出解决方案:
问题根源:getDerivedStateFromProps的触发时机
getDerivedStateFromProps和旧的UNSAFE_componentWillReceiveProps最大的区别在于触发时机:
UNSAFE_componentWillReceiveProps只在父组件传入新props时触发,组件自身state更新不会触发它;getDerivedStateFromProps会在每次组件渲染前被调用,包括组件自身state更新导致的渲染。
你的当前逻辑是:只要nextProps.count !== prevState.count就返回新state,把count设为props的值。当你点击+/-按钮修改Counter的本地state后,组件重新渲染,此时父组件的props.count(Main的initialCount)并没有变化,但Counter的本地state.count已经变了,所以nextProps.count !== prevState.count条件成立,直接把state.count改回了props的值,看起来就像是被重置了。
解决方案
方案1:使用受控组件模式(最推荐)
让Counter完全由父组件控制状态,自身不维护count的state,所有修改都通过回调通知父组件更新state,再通过props传回来。这种方式逻辑清晰,也符合React的单向数据流设计:
class Counter extends React.Component { handleIncrease(e) { this.props.onChange(this.props.count + 1); } handleDecrease(e) { this.props.onChange(this.props.count - 1); } render() { return ( <div> <button onClick={this.handleIncrease.bind(this)}>+</button> {this.props.count} <button onClick={this.handleDecrease.bind(this)}>-</button> </div> ); } } class Main extends React.Component { constructor(props) { super(props); this.state = { initialCount: 1 }; } handleChange(e) { this.setState({ initialCount: parseInt(e.target.value) }); } handleCounterChange(newCount) { this.setState({ initialCount: newCount }); } render() { return ( <div> <Counter count={this.state.initialCount} onChange={this.handleCounterChange.bind(this)} /> <hr/> Change initial:<input type="number" onChange={this.handleChange.bind(this)} value={this.state.initialCount} /> </div> ); } } ReactDOM.render(<Main/>, document.getElementById("root"));
方案2:调整getDerivedStateFromProps的判断逻辑
如果你确实需要让Counter维护本地state,同时响应props变化,可以在组件中记录上一次的props值,只在props真正变化时更新state:
class Counter extends React.Component { constructor(props) { super(props); this.state = { count: props.count, prevPropsCount: props.count // 记录上一次接收的props值 }; } static getDerivedStateFromProps(nextProps, prevState) { // 只有当新props和上一次记录的props不同时,才更新state if(nextProps.count !== prevState.prevPropsCount) { return { count: nextProps.count, prevPropsCount: nextProps.count }; } return null; } handleIncrease(e) { this.setState({count: this.state.count + 1}) } handleDecrease(e) { this.setState({count: this.state.count - 1}) } render() { return ( <div> <button onClick={this.handleIncrease.bind(this)}>+</button> {this.state.count} <button onClick={this.handleDecrease.bind(this)}>-</button> </div> ); } } // Main组件保持不变 class Main extends React.Component { constructor(props) { super(props); this.state = { initialCount: 1 }; } handleChange(e) { this.setState({initialCount: parseInt(e.target.value)}) } render() { return ( <div> <Counter count={this.state.initialCount} /> <hr/> Change initial:<input type="number" onChange={this.handleChange.bind(this)} value={this.state.initialCount} /> </div> ) } } ReactDOM.render(<Main/>, document.getElementById("root"));
方案3:改用函数组件+useEffect(现代React推荐)
如果可以切换到函数组件,用useEffect监听props变化来更新本地state,逻辑会更直观:
function Counter({ count: initialCount }) { const [count, setCount] = useState(initialCount); // 仅当父组件传入的initialCount变化时,更新本地count useEffect(() => { setCount(initialCount); }, [initialCount]); const handleIncrease = () => setCount(count + 1); const handleDecrease = () => setCount(count - 1); return ( <div> <button onClick={handleIncrease}>+</button> {count} <button onClick={handleDecrease}>-</button> </div> ); } function Main() { const [initialCount, setInitialCount] = useState(1); const handleChange = (e) => { setInitialCount(parseInt(e.target.value)); }; return ( <div> <Counter count={initialCount} /> <hr /> Change initial: <input type="number" onChange={handleChange} value={initialCount} /> </div> ); } ReactDOM.render(<Main />, document.getElementById("root"));
总结
你的getDerivedStateFromProps用法确实存在逻辑漏洞,错误地用当前state和新props做比较,没有区分渲染触发的原因。最推荐的是方案1(受控组件)或方案3(函数组件+useEffect),这两种方式代码更简洁,也更符合React的设计思想。
内容的提问来源于stack exchange,提问作者ismail codar

