Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mobx getDerivedStateFromProps 不执行 包括 componentWillReceiveProps #4176

Closed
hnsylitao opened this issue Aug 15, 2019 · 15 comments
Closed
Assignees

Comments

@hnsylitao
Copy link

问题描述
taro init 初始project
模板是mobx
为什么 加了 getDerivedStateFromProps之后 props 不会重新更新了

复现步骤
[复现问题的步骤]

import { ComponentType } from 'react'
import Taro, { Component, Config } from '@tarojs/taro'
import { View, Button, Text } from '@tarojs/components'
import { observer, inject } from '@tarojs/mobx'

import './index.less'

type PageStateProps = {
  counterStore: {
    counter: number,
    increment: Function,
    decrement: Function,
    incrementAsync: Function
  }
}

interface Index {
  props: PageStateProps;
}

@inject('counterStore')
@observer
class Index extends Component {

  /**
   * 指定config的类型声明为: Taro.Config
   *
   * 由于 typescript 对于 object 类型推导只能推出 Key 的基本类型
   * 对于像 navigationBarTextStyle: 'black' 这样的推导出的类型是 string
   * 提示和声明 navigationBarTextStyle: 'black' | 'white' 类型冲突, 需要显示声明类型
   */
  config: Config = {
    navigationBarTitleText: '首页'
  }

  static getDerivedStateFromProps(props,state) {
    console.log('getDerivedStateFromProps')
    return null;
  }

  componentWillReceiveProps () {
    console.log('componentWillReceiveProps')
   }

  componentWillMount () { }

  componentWillReact () {
    console.log('componentWillReact')
  }

  componentDidMount () { }

  componentWillUnmount () { }

  componentDidShow () { }

  componentDidHide () { }

  increment = () => {
    const { counterStore } = this.props
    counterStore.increment()
  }

  decrement = () => {
    const { counterStore } = this.props
    counterStore.decrement()
  }

  incrementAsync = () => {
    const { counterStore } = this.props
    counterStore.incrementAsync()
  }

  render () {
    const { counterStore: { counter } } = this.props
    return (
      <View className='index'>
        <Button onClick={this.increment}>+</Button>
        <Button onClick={this.decrement}>-</Button>
        <Button onClick={this.incrementAsync}>Add Async</Button>
        <Text>{counter}</Text>
      </View>
    )
  }
}

export default Index  as ComponentType

期望行为
[这里请用简洁清晰的语言描述你期望的行为]

报错信息

[这里请贴上你的完整报错截图或文字]

系统信息

Taro v1.2 及以上版本已添加 taro info 命令,方便大家查看系统及依赖信息,运行该命令后将结果贴下面即可。

Taro CLI 1.3.4 environment info:
System:
OS: Windows 10
Binaries:
Node: 10.15.3 - D:\Application\Development\nodejs\node.EXE
Yarn: 1.16.0 - D:\Application\Development\npm\yarn.CMD
npm: 6.4.1 - D:\Application\Development\nodejs\npm.CMD

@taro-bot
Copy link

taro-bot bot commented Aug 15, 2019

欢迎提交 Issue~

如果你提交的是 bug 报告,请务必遵循 Issue 模板的规范,尽量用简洁的语言描述你的问题,最好能提供一个稳定简单的复现。🙏🙏🙏

如果你的信息提供过于模糊或不足,或者已经其他 issue 已经存在相关内容,你的 issue 有可能会被关闭。

Good luck and happy coding~

@hnsylitao
Copy link
Author

阅读了taro-mobx-common 的源码
以及 官方文档 还有 以及一些人提出的issue
看到了几个问题
根据官方文档中给出 https://nervjs.github.io/taro/docs/apis/about/tarocomponent.html#static-getderivedstatefromprops

请注意: getDerivedStateFromProps() 如果存在,componentWillReceiveProps、componentWillMount 和 componentWillUpdate 将不会调用。 当你需要在以上老生命周期 setState 时,我们更推荐你使用 getDerivedStateFromProps 方法,因为它能减少一次更新开销。

以及 mobx-common 中的实现 https://github.com/NervJS/taro/blob/4d950d0919/packages/taro-mobx-common/src/observer.js#L17-L27

可以看出 当存在static getDerivedStateFromProps时 不会触发 taro-mobx-common 的 componentWillMount
当然也就不会触发后面相关的任何绑定。

于是我看了下redux 之前也遇到了同样的问题的issue #3929
以及解决issue的提交 4d950d0

发现是把周期事件从 componentWillMount 变成 _constructor

原谅我不知道 _constructor 是你们内部实现的什么机制,我猜想是 constructor 的调用
所以我就把willMount 改成 _constructor 结果 constructor调用两次(按照小程序的理解是被理解的)
https://nervjs.github.io/taro/docs/best-practice.html#%E7%BB%84%E4%BB%B6%E7%9A%84-constructor-%E4%B8%8E-render-%E6%8F%90%E5%89%8D%E8%B0%83%E7%94%A8

结果是 getDerivedStateFromProps componentWillReact 触发了两遍
相比taro-redux 也有这样的问题把。

我想知道接下来taro 团队怎么解决这个问题。

@hnsylitao
Copy link
Author

hnsylitao commented Aug 15, 2019

按照上面的问题,我的暂时解决是

验证之后 getDerivedStateFromProps 会触发 但是 componentWillReceiveProps 还是不会触发

export function observer (component) {
  if (isUsingStaticRendering()) {
    return component
  }

  if (component.isMobxInjector === true) {
    console.warn(
      "Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'"
    )
  }

  const target = component.prototype
  const originConstructor = target._constructor
  const originComponentWillReact = target.componentWillReact
  target._constructor = function (...args) {
    const initialName = this.displayName || this.name
    this._reaction = new Reaction(`${initialName}_${Date.now()}`, () => {
      this.forceUpdate()
      originComponentWillReact && originComponentWillReact.call(this)
    })
    originConstructor && originConstructor.call(this,...args)
  }

@hnsylitao
Copy link
Author

看了下taro-weapp 的源码

能否在 this.forceUpdate() 调用前 设置 this._unsafeCallUpdate = true
调用后重置回 false

不知道这样有什么误解,对于_unsafeCallUpdate 不是很清楚这里主要的作用是为了防止什么

@hnsylitao
Copy link
Author

hnsylitao commented Aug 15, 2019

按照上面的问题,我的暂时解决是

验证之后 getDerivedStateFromProps 会触发 但是 componentWillReceiveProps 还是不会触发

export function observer (component) {
  if (isUsingStaticRendering()) {
    return component
  }

  if (component.isMobxInjector === true) {
    console.warn(
      "Mobx observer: You are trying to use 'observer' on a component that already has 'inject'. Please apply 'observer' before applying 'inject'"
    )
  }

  const target = component.prototype
  const originConstructor = target._constructor
  const originComponentWillReact = target.componentWillReact
  target._constructor = function (...args) {
    const initialName = this.displayName || this.name
    this._reaction = new Reaction(`${initialName}_${Date.now()}`, () => {
      this.forceUpdate()
      originComponentWillReact && originComponentWillReact.call(this)
    })
    originConstructor && originConstructor.call(this,...args)
  }

这里还缺少一个环境判断,,应该只是在小程序中才会有这样的机制

@cloudZQY
Copy link
Contributor

function hasNewLifecycle(component) {
  var getDerivedStateFromProps = component.constructor.getDerivedStateFromProps,
      getSnapshotBeforeUpdate = component.getSnapshotBeforeUpdate;
  return isFunction(getDerivedStateFromProps) || isFunction(getSnapshotBeforeUpdate);
}

看了一下源码,当有getDerivedStateFromProps的时候不会触发componentWillReact 。在上面这里判断的。

target.componentWillMount = function () {
    var _this = this;

    var initialName = this.displayName || this.name;
    this._reaction = new mobx.Reaction("".concat(initialName, "_").concat(Date.now()), function () {
      _this.forceUpdate();

      originComponentWillReact && originComponentWillReact.call(_this);
    });
    originComponentWillMount && originComponentWillMount.call(this);
  };

而在@tarojs/mobx-common中,mobx进行React初始化是在componentWillMount这个生命周期中进行的,不执行componentWillMount会导致mobx无法进行依赖收集。

@hnsylitao
Copy link
Author

@cloudZQY
根据官方文档中给出 https://nervjs.github.io/taro/docs/apis/about/tarocomponent.html#static-getderivedstatefromprops

请注意: getDerivedStateFromProps() 如果存在,componentWillReceiveProps、componentWillMount 和 componentWillUpdate 将不会调用。 当你需要在以上老生命周期 setState 时,我们更推荐你使用 getDerivedStateFromProps 方法,因为它能减少一次更新开销。

@cloudZQY
Copy link
Contributor

@cloudZQY
根据官方文档中给出 https://nervjs.github.io/taro/docs/apis/about/tarocomponent.html#static-getderivedstatefromprops

请注意: getDerivedStateFromProps() 如果存在,componentWillReceiveProps、componentWillMount 和 componentWillUpdate 将不会调用。 当你需要在以上老生命周期 setState 时,我们更推荐你使用 getDerivedStateFromProps 方法,因为它能减少一次更新开销。

所以这是对于mobx的一个bug,可能需要把这段代码挪到contructor里面去。但是不知道会有什么其他副作用

@hnsylitao
Copy link
Author

在小程序中回触发两次contructor 会导致 两次触发依赖收集
我这里判断了ready 好了才 触发依赖收集,但是对于小程序来说 getDerivedStateFromProps 还是收集不到 不在createData 里面的props

@nanjingboy
Copy link
Member

nanjingboy commented Sep 2, 2019

@hnsylitao 已提 PR:#4366

另外:

  • componentWillReceiveProps 不被调用在 mobx-react 中属于正常行为,这里采用了相关策略。
  • 小程序的局限性,需要监听的属性不要在 render(小程序中即为 _createData) 方法之外使用。

@taro-bot
Copy link

taro-bot bot commented Sep 2, 2019

Hello~

您的问题楼上已经提供了解决方案,如果没有更多的问题这个 issue 将在 15 天后被自动关闭。

如果您在这 15 天中更新更多信息自动关闭的流程会自动取消,如有其他问题也可以发起新的 Issue。

Good luck and happy coding~

@hnsylitao
Copy link
Author

@nanjingboy
相关的代码我已经看到了,等带发布之后,测试验证

另外你说的这个 componentWillReceiveProps 和我说的 static getDerivedStateFromProps 还是taro 的一个规则问题。

但是你说的componentWillReceiveProps 没有在render 中被 监听 可以通过以下的策略来调整。
如果需要突破这种小程序策略的问题,
可以通过在_createData 加载一个key
每次修改mobx 值得时候顺带修改 key 值来操作到

@nanjingboy
Copy link
Member

@hnsylitao 是有办法触发 componentWillReceiveProps ,但我们需要保证各端行为的一致,所以按照 mobx-react 的做法来处理。

@taro-bot taro-bot bot removed the to be closed label Sep 17, 2019
@taro-bot taro-bot bot closed this as completed Sep 17, 2019
@hnsylitao
Copy link
Author

hnsylitao commented Mar 21, 2020

这个问题发现了一个副作用,如果mobx 中的值没有在 render 中使用
那么在static getDerivedStateFromProps也不会触发,我希望这个问题能够有好的方法避免。
@nanjingboy

@nanjingboy
Copy link
Member

@hnsylitao 我们监听的就是 render 方法,如没有在 render 中使用,便不会触发重绘,这是基本规则,不是问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants