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

[微信小程序]嵌套Component不能正确触发componentWillReceiveProps生命周期 #5231

Closed
Aliveing opened this issue Jan 3, 2020 · 20 comments
Assignees
Labels
question Further information is requested

Comments

@Aliveing
Copy link

Aliveing commented Jan 3, 2020

问题描述

A组件嵌套了B组件,A组件中把state值当作props传给B,并在A的componentDidMount中设置 A新的state,并在B的componentDidMount中设置自己新的state,B组件的componentWillReceiveProps会触发两次,并且两次nextProps和当前props值相同,作为组件类普遍都在 componentWillReceiveProps事件中比较props值,来运行组件功能代码或者保存新的实例变量数据,这个bug就导致比较props值相同,这种触发机制用着挺难受

复现步骤

talk is cheap show you the code

A 组件

import Taro, { Component } from '@tarojs/taro';
import { View } from '@tarojs/components';
import B from './B';

class A extends Component {
    constructor(props) {
        super(props);
        this.state = {}
    }

    componentDidMount() {
        console.log('--------------class A didMount', this.state.value);
        this.setState({ value: 1 }, () => {
            console.log('--------------class A didMount & after setState', this.state.value);
        });
    }

    render() {
        const { value } = this.state;
        return <View>
            AAAAAAAAAAAAA
            <B value={value} />
        </View>
    }
}
export default A;

B组件

import Taro, { Component } from '@tarojs/taro';
import { View } from '@tarojs/components';

class B extends Component {

    state = { didMount: false }

    componentDidMount() {
        console.log('--------------class B did mount', this.props.value);
        this.setState({ didMount: true }, () => {
            console.log('--------------class B didMount & after setState', this.state.didMount);
        });
    }

    componentWillReceiveProps(nextProps) {
        console.log('--------------class B receive props', nextProps.value, this.props.value, this.state.didMount);
    }

    render() {
        console.log('--------------class B render', this.props.value);
        const { value } = this.props;
        const { didMount } = this.state;
        return <View>{value}{didMount}</View>
    }
}
export default B;

期望行为

被嵌套的组件能正确触发componentWillReceiveProps生命周期

报错信息

生命周期触发错误,没有报错信息

系统信息

👽 Taro v1.3.25

Taro CLI 1.3.25 environment info:
System:
OS: macOS 10.15.2
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 10.16.3 - ~/.nvm/versions/node/v10.16.3/bin/node
Yarn: 1.19.2 - ~/.yarn/bin/yarn
npm: 6.9.0 - ~/.nvm/versions/node/v10.16.3/bin/npm
npmPackages:
@tarojs/components: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/plugin-babel: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/plugin-csso: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/plugin-sass: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/plugin-uglifyjs: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/router: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-alipay: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-h5: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-qq: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-quickapp: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-swan: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-tt: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/taro-weapp: 2.0.0-beta.13 => 2.0.0-beta.13
@tarojs/webpack-runner: 2.0.0-beta.13 => 2.0.0-beta.13
eslint-config-taro: 2.0.0-beta.13 => 2.0.0-beta.13
eslint-plugin-taro: 2.0.0-beta.13 => 2.0.0-beta.13
nerv-devtools: ^1.5.6 => 1.5.6
nervjs: ^1.5.6 => 1.5.6
stylelint-config-taro-rn: 2.0.0-beta.13 => 2.0.0-beta.13
stylelint-taro-rn: 2.0.0-beta.13 => 2.0.0-beta.13

补充信息

生命周期触发问题,没有等到A组件真正渲染完毕就触发componentDidMount

如果您有功能上的建议,可以提到 FeatHub

使用上的问题,欢迎在「Taro 社区」一起交流

@taro-bot
Copy link

taro-bot bot commented Jan 3, 2020

CC @Chen-jj

@taro-bot
Copy link

taro-bot bot commented Jan 3, 2020

欢迎提交 Issue~

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

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

Good luck and happy coding~

@Aliveing
Copy link
Author

Aliveing commented Jan 3, 2020

暂时的解决办法:在A的 componentDidMount 事件中用setTimeout来设置state。

A组件

componentDidMount() {
    setTimeout(() => this.setState({value: 1}), 0);
}

@Chen-jj
Copy link
Contributor

Chen-jj commented Jan 3, 2020

@Aliveing CLI 和依赖版本保持一致

@Aliveing
Copy link
Author

Aliveing commented Jan 3, 2020

@Chen-jj 统一使用1.3.34版本,issue中的问题依然存在。

taro info(更新为CLI与依赖一致)

👽 Taro v1.3.34

Taro CLI 1.3.34 environment info:
System:
OS: macOS 10.15.2
Shell: 5.7.1 - /bin/zsh
Binaries:
Node: 10.16.3 - ~/.nvm/versions/node/v10.16.3/bin/node
Yarn: 1.19.2 - ~/.yarn/bin/yarn
npm: 6.9.0 - ~/.nvm/versions/node/v10.16.3/bin/npm
npmPackages:
@tarojs/components: 1.3.34 => 1.3.34
@tarojs/plugin-babel: 1.3.34 => 1.3.34
@tarojs/plugin-csso: 1.3.34 => 1.3.34
@tarojs/plugin-sass: 1.3.34 => 1.3.34
@tarojs/plugin-uglifyjs: 1.3.34 => 1.3.34
@tarojs/router: 1.3.34 => 1.3.34
@tarojs/taro: 1.3.34 => 1.3.34
@tarojs/taro-alipay: 1.3.34 => 1.3.34
@tarojs/taro-h5: 1.3.34 => 1.3.34
@tarojs/taro-qq: 1.3.34 => 1.3.34
@tarojs/taro-quickapp: 1.3.34 => 1.3.34
@tarojs/taro-swan: 1.3.34 => 1.3.34
@tarojs/taro-tt: 1.3.34 => 1.3.34
@tarojs/taro-weapp: 1.3.34 => 1.3.34
@tarojs/webpack-runner: 1.3.34 => 1.3.34
eslint-config-taro: 1.3.34 => 1.3.34
eslint-plugin-taro: 1.3.34 => 1.3.34
nerv-devtools: ^1.5.6 => 1.5.6
nervjs: ^1.5.6 => 1.5.6
stylelint-config-taro-rn: 1.3.34 => 1.3.34
stylelint-taro-rn: 1.3.34 => 1.3.34

@Chen-jj
Copy link
Contributor

Chen-jj commented Jan 3, 2020

@Aliveing Page 和 Component 的 componentDidMount 分别对应小程序的 onReady 和 ready。

如果 A 是页面、B 是组件,componentWillReceiveProps 的 nextProps 是会有可能等于 this.props。这是因为页面的 onReady 触发时间有可能在所有页面 ready 前或所有页面 ready 后,onReady 最先触发的情况下就会有问题,这里你只能换种写法。

但如果 A 和 B 都是组件,B ready 肯定比 A 早,因此 componentWillReceiveProps 的 nextProps 没有错。

至于 componentWillReceiveProps 会触发两次,复现不了。

@Chen-jj Chen-jj added answered question Further information is requested labels Jan 3, 2020
@taro-bot taro-bot bot added the to be closed label Jan 3, 2020
@taro-bot
Copy link

taro-bot bot commented Jan 3, 2020

Hello~

您的问题楼上已经有了确切的回答,如果没有更多的问题这个 issue 将在 15 天后被自动关闭。

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

Good luck and happy coding~

@Aliveing
Copy link
Author

Aliveing commented Jan 3, 2020

@Chen-jj 我把复现代码更新了一下,让console更详细一些,这个问题核心就在于 B 的 componentWillReceiveProps触发了两次并且这两次nextProps和this.props都相同,并且不是概率性的,而这两次的原因只有两个来源,一个是B didMount的时候更新state,一个是A didMount时更新state导致B的props改变。
为了方便查看以下是运行的结果:

1. --------------class B render undefined   // (B.props.value)
2. --------------class B did mount undefined  // (B.props.value & B setState didMount=true)
3. --------------class A didMount undefined  // (A.state.value & A setState value=1)
4. --------------class B render 1  // (B.props.value)
5. --------------class B receive props 1 1 true  // (nextProps.value B.props.value B.state.didMount)
6. --------------class B render 1  // (B.props.value)
7. --------------class B receive props 1 1 true // (nextProps.value B.props.value B.state.didMount)
8. --------------class B render 1  // (B.props.value)
9. --------------class A didMount & after setState 1  // (A.state.value)
10. --------------class B didMount & after setState true  // (B.state.didMount)

问题就是:

  1. 第5行 B receiveProps 和 第7行 B receiveProps 分别是因为什么触发的?
  2. 为什么在A的componentDidMount中,用 setTimeout 0 设置state的方式就能正确触发componentWillReceiveProps? (经过尝试发现在B的 componentDidMount 中用同样的setTimeout方法也可以正确触发,即 A或者B 中只要其中一个在 componentDidMount 中用 setTimeout 方式更新就能正确触发componentWillReceiveProps事件)

@Aliveing
Copy link
Author

Aliveing commented Jan 4, 2020

@Chen-jj 今天在开发的时候发现 componentWillReceiveProps 生命周期的触发条件真的非常迷,而且传来的数据也是迷到不行,当props改变时有概率不触发,导致写的组件在某些概率下不能根据传来的数据加载值,浪费了整整两天时间。

如果taro在这个生命周期上有问题,应该写在文档里告诉开发尽量不去使用,或者什么情况下才能避免这种情况的发生,而在官方开发文档的 组件生命周期 中只字未提,我个人其实不在意taro因为各平台的原因而导致某些写法不能支持,只不过taro说是可以用react编写不同平台,但是这种常用生命周期的差异性为何不提?为何不在开发文档中提前告知开发者,而是让开发者自己在开发时发现问题,并在github上以为是bug提交后再被贴一个 question 的标签,希望taro能将这种差异性补充到各端开发前注意

终于还是放弃了这个生命周期,用shouldComponentUpdate + componentDidUpdate 来模拟,虽然写的麻烦点,但是没有所谓概率性触发问题。

@Chen-jj
Copy link
Contributor

Chen-jj commented Jan 6, 2020

@Aliveing 还是维持我上述回答,componentWillReceiveProps 并没复现调用多次。你没说 A 是页面还是组件,如果 A 是页面,那不要这样写,小程序 Page 的 onReady 触发时机不稳定。但如果 A 是组件,那完全没有问题。

  1. A 是页面,情况一:

image

  1. A 是页面,情况二:

image

  1. A 是组件:

image

均没有复现 componentWillReceiveProps 调用多次。

@Aliveing
Copy link
Author

Aliveing commented Jan 6, 2020

@Chen-jj 好的,我依次说明下:

  1. A是界面,B是组件,微信开发工具版本(1.02.1911180)
  2. 关于没有两次 componentWillReceiveProps 的问题,我不太清楚你的结果为何只有一次,但是咱们从代码角度分析也应该是两次(以下分析不分顺序,因为我想问的就是触发顺序和传参的正确性)
  • 第一个是A在componentDidMount中更改自己state而导致B的props value变化
  • 第二个是B在componentDidMount中更新自己state

以下是我运行的console截图:

奇怪的B.props

image

正常的B.props

image

@Chen-jj
Copy link
Contributor

Chen-jj commented Jan 6, 2020

@Aliveing 额,setState 怎会触发 componentWillReceiveProps。这里只应触发一次。

A 是页面,componentDidMount 由 onReady 触发,微信小程序页面 onReady 触发时机不稳定。这时你不要强依赖 componentDidMount 的顺序做逻辑便是。

@Aliveing
Copy link
Author

Aliveing commented Jan 6, 2020

@Chen-jj 抱歉是我记错了,去查了查 react 文档,和getDerivedStateFromProps生命周期记混了,不过触发两次componentWillReceiveProps通过复现代码在我这边是100%的概率,我再重新建一个新项目试试。

以下是我当前工程中的package.json文件

package.json 截图
image

@Aliveing
Copy link
Author

Aliveing commented Jan 6, 2020

@Chen-jj 首先感谢一直跟踪回复,我试了试,新建的项目确实没问题,挨个排查以后,十分惊奇的是,taro-listview这个组件会导致两次触发componentWillReceiveProps,只要新建的项目安上了这个组件,即使工程中没有引用taro-listview也会发生这个触发两次的bug。
原因目前不详,猜测是taro-listview的package.json引用了老版本的taro,编译的时候导致taro版本被覆盖了,如果是这样的话,taro有没有什么方法可以获取到当前编译使用的taro版本是什么?
还有就是有个疑问,一个三方组件,为什么可以影响到taro整体的生命周期?

@Aliveing
Copy link
Author

Aliveing commented Jan 7, 2020

重新fork了一下 taro-listview,把其中 package.json 做了以下改动

  • 将固定taro 1.3.15 版本改为获取最新版本
  • 依赖方式从 dependencies 改为 peerDependencies

这样安装完 taro-listview 后 node_modules/taro-listview/node_modules 里就没有老旧版本的taro了,两次触发componentWillReceiveProps的问题也就好了。
但是我还是依然想知道taro在编译三方组件的时候如果版本不统一,为何会出现这种奇怪的问题? @Chen-jj

@Garfield550
Copy link
Collaborator

@Aliveing
Copy link
Author

Aliveing commented Jan 7, 2020

嗯... 我理解npm的依赖处理机制,不过经过测试有意思的来了,如果只在 taro-listview 环境下(taro@1.3.15)就不会出现触发两次的情况,只有在这两个版本(taro@1.3.15 & taro@1.3.34)同时存在时,才会触发两次,可能之前我没表达清楚,重新描述一下问题:
为什么在工程中我引用了一个 taro 的三方组件,而这个组件使用了与当前工程不同的 taro 版本,经过 npm run dev:weapp 编译后会导致工程内组件的生命周期 componentWillReceiveProps 触发两次?

@Garfield550
Copy link
Collaborator

一定程度上是 CLI 的 AST 分析问题。

@Aliveing
Copy link
Author

Aliveing commented Jan 7, 2020

所以现阶段只能先这样,然后等待 taro-cli AST能正确转译?

@taro-bot taro-bot bot removed the to be closed label Jan 18, 2020
@taro-bot taro-bot bot closed this as completed Jan 18, 2020
@Aliveing
Copy link
Author

这... 关闭了?CLI 的 AST 分析问题已经解决了?

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

No branches or pull requests

3 participants