我们大多数情况下需要getDerivedStateFromProps吗

2,974 阅读3分钟

使用背景

我很不喜欢在代码里写componentWillReceiveProps,因为这样会导致子组件太过灵活,你并不知道何时就改掉了子组件的state。但是,当子组件需要根据父组件更新的props中的某个属性来更新自身的state来做到重新渲染子组件时,我又只能鬼使神差的写下这么一段自己都不喜欢的代码:

class Demo extends Component {  
    state = {    
        value: this.props.value  
    };​   
    componentWillReceiveProps(props){    
        if(props.value !== this.state.value){      
            this.setState({value:props.value})    
        }    
        if(this.props.value){      
        // 做一些需要this.props的事    
        }  
    }    
    // ...
}

这种代码应该在实际React应用中很常见,在新版本的React中,componentWillReceiveProps被标记为unsafe,同时,官方出了一个新的生命周期getDerivedStateFromProps,官宣称配合componentDidUpdate,可以覆盖componentWillReceiveProps的所有用法

说明书

componentWillReceiveProps可以使用this.props,不仅可以做setState操作,还可以和this.props进行比对去做一些日常的方法的操作。

componentWillReceiveProps(props){  
    if(props.value !== this.state.value){    
        this.setState({value:props.value})  
    }  
    if(this.props.value){   
         // 做一些需要this.props的事  
    }
}

getDerivedStateFromProps禁止访问this.props,强制指定props只能和state进行比对,可以说是为setState而创建的。

static getDerivedStateFromProps(props,state){    
    if(props.value !== state.value){      
        return {        
            value:props.value      
        }    
    }    
    return null
}

而那些需要this.props做的事则通过componentDidUpdate来做,也就解释了上面所说的getDerivedStateFromProps + componentDidUpdate = componentWillReceiveProps

componentDidUpdate(){  
    if(this.props.value){    
    // 做一些需要this.props的事  
    }
}

可以说新的生命周期使得什么时候做什么事变得更清晰了,通过强制的不暴露this.props来将componentWillReceiveProps拆分成了拥有各自职责的getDerivedStateFromProps和componentDidUpdate

这俩只有这一种区别吗?

食用测试

我们分别在父组件的render和子组件的componentWillReceiveProps和getDerivedStateFromProps打印日志

父组件

class Index extends React.Component {  
    // ...  
    render(){    
        console.log('父组件更新')    
        <Demo value={this.state.value}>  
    }
}

子组件使用componentWillReceiveProps

class Demo extends Component {  
    state = {    
        value: this.props.value      
    };​   
    componentWillReceiveProps(props){    
        console.log('componentWillReceiveProps')    
        if(props.value !== this.state.value){      
            this.setState({value:props.value})    
        }  
    }  
    render(){    
        console.log('子组件更新')     
        // ...  
    }
}

子组件初始化打印结果:

父组件更新
子组件更新

子组件更新state.value的打印结果:

子组件更新
父组件更新
componentWillReceiveProps
子组件更新

2.子组件使用getDerivedStateFromProps

class Demo extends Component {  
    state = {    
        value: this.props.value  
    };​  
    static getDerivedStateFromProps(props,state){    
        console.log('getDerivedStateFromProps')    
        if(props.value !== state.value){      
            return {        
                value:props.value      
            }    
        }    
        return null  
    }  
    render(){    
        console.log('子组件更新')     
        // ...  
    }
}

子组件初始化打印结果:

父组件更新
getDerivedStateFromProps
子组件更新

子组件更新state.value的打印结果:

getDerivedStateFromProps
子组件更新
父组件更新
getDerivedStateFromProps
子组件更新

这让我们更清晰的认识了getDerivedStateFromProps函数,他不仅在props变化的时候触发,而是在每次render(state和props更新)时就会触发

不只是子组件本身的render,当父组件一个无关本组件的state发生了变化,也会触发getDerivedStateFromProps

大多数情况下替代componentWillReceiveProps的方法

当我打算将getDerivedStateFromProps取代我的componentWillReceiveProps时,看到了一篇来自React的官方blog 你可能不需要使用派生 state,虽然是去年的文章了,但是看到依然觉得有种想要双击666的感觉。

熟悉React的童鞋都知道,key值是否相同是React决定是否重新创建还是更新组件的原因。

同样的,我们可以采用这个原理做我们刚才的范例。

在我们的父组件设置好key值:

<Demo key={item.value} callback={changeValue}>

去掉子组件里的getDerivedStateFromProps/componentWillReceiveProps

每次这个Demo组件的state.value更改,触发了props的更改,我们之前在getDerivedStateFromProps所做的就是在props更改时同时改掉子组件的state。

现在我们通过赋值key的方法,使每次key变动时,重新创建Demo组件,这听起来很慢,但是这点的性能是可以忽略的。如果Demo组件树上有很重的逻辑时,创建反而比更新快,因为省略了子组件的diff

大部分情况下,这是处理重置 state 的最好的办法。