React新生命周期--getDerivedStateFromProps

React 16是最近一年多React更新最大的版本。除了让大家喜闻乐见的向下兼容的Fiber,防止了客户端react在进行渲染的时候阻塞页面的其他交互行为。Fiber源码速览
参考https://juejin.im/post/5bea68a6e51d450cb20fdd70

新的生命周期过程

先来看看最新版本react的生命周期图:


image.png

看看它的变化

新增:getDerivedStateFromProps,getSnapshotBeforeUpdate
UNSAFE:UNSAFE_componentWillMount,UNSAFE_componentWillUpdate,UNSAFE_componentWillReceiveProps

getDerivedStateFromProps

React生命周期的命名一直都是非常语义化的,这个生命周期的意思就是从props中获取state,可以说是太简单易懂了。

可以说,这个生命周期的功能实际上就是将传入的props映射到state上面

由于16.4的修改,这个函数会在每次re-rendering之前被调用,这意味着什么呢?

意味着即使你的props没有任何变化,而是父state发生了变化,导致子组件发生了re-render,这个生命周期函数依然会被调用。看似一个非常小的修改,却可能会导致很多隐含的问题。

使用

这个生命周期函数是为了替代componentWillReceiveProps存在的,所以在你需要使用componentWillReceiveProps的时候,就可以考虑使用getDerivedStateFromProps来进行替代了。

两者的参数是不相同的,而getDerivedStateFromProps是一个静态函数,也就是这个函数不能通过this访问到class的属性,也并不推荐直接访问属性。而是应该通过参数提供的nextProps以及prevState来进行判断,根据新传入的props来映射到state。

需要注意的是,如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的,所以尽量将其写到函数的末尾。

static getDerivedStateFromProps(nextProps, prevState) {
    const {type} = nextProps;
    // 当传入的type发生变化的时候,更新state
    if (type !== prevState.type) {
        return {
            type,
        };
    }
    // 否则,对于state不进行任何操作
    return null;
}

Case1 -- 多来源的不同状态

假设我们有一个列表,这个列表受到页面主体,也就是根组件的驱动,也受到其本身数据加载的驱动。

因为这个页面在开始渲染的时候,所有的数据请求可能是通过batch进行的,所以要在根组件进行统一处理,而其列表的分页操作,则是由其本身控制。

这会出现什么问题呢?该列表的状态受到两方面的控制,也就是re-render可能由props驱动,也可能由state驱动。这就导致了getDerivedStateFromProps会在两种驱动状态下被重新渲染。

当这个函数被多次调用的时候,就需要注意到,state和props的变化将会怎样影响到你的组件变化。

// 组件接收一个type参数
static propTypes = {
    type: PropTypes.number
}

// 组件还具有自己的状态来渲染列表
class List extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            list: [],
            type: 0,
        }
    }
}

如上面代码的例子所示,组件既受控,又控制自己。当type发生变化,会触发一次getDerivedStateFromProps,在这里更新组件的type状态,然而,在进行异步操作之后,组件又会更新list状态,这时你的getDerivedStateFromProps函数就需要注意,不能够仅仅判断type是否变化来更新状态,因为list的变化也会更新到组件的状态。这时就必须返回一个null,否则会导致组件无法更新并且报错。

Case2 -- 组织好你的组件

考虑一下,如果你的组件内部既需要修改自己的type,又需要接收从外部修改的type。

是不是非常混乱?getDerivedStateFromProps中你根本不知道该做什么。

static getDerivedStateFromProps(nextProps, prevState) {
    const {type} = nextProps;
    // type可能由props驱动,也可能由state驱动,这样判断会导致state驱动的type被回滚
    if (type !== prevState.type) {
        return {
            type,
        };
    }
    // 否则,对于state不进行任何操作
    return null;
}

如何解决这个棘手的问题呢?

好好组织你的组件,在非必须的时候,摒弃这种写法。type要么由props驱动,要么完全由state驱动。
如果实在没有办法解耦,那么就需要一个hack来辅助:绑定props到state上。

constructor(props) {
    super(props);
    this.state = {
        type: 0,
        props,
    }
}
static getDerivedStateFromProps(nextProps, prevState) {
    const {type, props} = nextProps;
    // 这段代码可能看起来非常混乱,这个props可以被当做缓存,仅用作判断
    if (type !== props.type) {
        return {
            type,
            props: {
                type,
            },
        };
    }
    // 否则,对于state不进行任何操作
    return null;
}

上面的代码可以保证在进行多数据源驱动的时候,状态能够正确改变。当然,这样的代码很多情况下是会影响到别人阅读你的代码的,对于维护造成了非常大的困难。

从这个生命周期的更新来看,react更希望将受控的propsstate进行分离,就如同Redux作者Dan Abramov在redux文档当中写的一样Presentational and Container Components,将所有的组件分离称为展示型组件和容器型组件,一个只负责接收props来改变自己的样式,一个负责保持其整个模块的state。这样可以让代码更加清晰。但是在实际的业务逻辑中,我们有时很难做到这一点,而且这样可能会导致容器型组件变得非常庞大以致难以管理,如何进行取舍还是需要根据实际场景决定的。

Case3 -- 异步

以前,我们可以在props发生改变的时候,在componentWillReceiveProps中进行异步操作,将props的改变驱动到state的改变。

componentWillReceiveProps(nextProps) {
    if (props.type !== nextProps.type) {
        // 在这里进行异步操作或者更新状态
        this.setState({
            type: props.type,
        });
        this._doAsyncOperation();
    }
}

这样的写法已经使用了很久,并且并不会存在什么功能上的问题,但是将componentWillReceiveProps标记为deprecated的原因也并不是因为功能问题,而是性能问题。

当外部多个属性在很短的时间间隔之内多次变化,就会导致componentWillReceiveProps被多次调用。这个调用并不会被合并,如果这次内容都会触发异步请求,那么可能会导致多个异步请求阻塞。

getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.

这个生命周期函数会在每次调用render之前被触发,而读过一点react源码的童鞋都会了解,reactsetState操作是会通过transaction进行合并的,由此导致的更新过程是batch的,而react中大部分的更新过程的触发源都是setState,所以render触发的频率并不会非常频繁(感谢 @leeenx20 的提醒,这里描述进行了修改)。

在使用getDerivedStateFromProps的时候,遇到了上面说的props在很短的时间内多次变化,也只会触发一次render,也就是只触发一次getDerivedStateFromProps。这样的优点不言而喻。

那么如何使用getDerivedStateFromProps进行异步的处理呢?

If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.

官方教你怎么写代码系列,但是其实也没有其他可以进行异步操作的地方了。为了响应props的变化,就需要在componentDidUpdate中根据新的props和state来进行异步操作,比如从服务端拉取数据。

// 在getDerivedStateFromProps中进行state的改变
static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.type !== prevState.type) {
        return {
            type: nextProps.type,
        };
    }
    return null;
}
// 在componentDidUpdate中进行异步操作,驱动数据的变化
componentDidUpdate() {
    this._loadAsyncData({...this.state});
}

小结

以上是本期开发过程中使用新的生命周期函数的时候遇到的一点小问题和一些相关思考。react为了防止部分开发者滥用生命周期,可谓非常尽心尽力了。既然你用不好,我就干脆不让你用。一个静态的生命周期函数可以让状态的修改更加规范和合理。

至于为什么全文没有提到getSnapshotBeforeUpdate,因为自己并没有用到#诚实脸。简单看了一下,这个函数返回一个update之前的快照,并且传入到componentDidUpdate中,组件更新前后的状态都可以在componentDidUpdate中获取了。一些需要在组件更新完成之后进行的操作所需要的数据,就可以不需要挂载到state或者是cache下来了。比如官方例子中所举例的保留更新之前的页面滚动距离,以便在组件update完成之后恢复其滚动位置。也是一个非常方便的周期函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容