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

【微信小程序】子组件里 map 生成的第2层子组件,在数据更新时无法正确获取props并更新 #4497

Closed
huey-LS opened this issue Sep 20, 2019 · 11 comments
Assignees
Labels
question Further information is requested

Comments

@huey-LS
Copy link
Contributor

huey-LS commented Sep 20, 2019

问题描述
子组件里 map 生成的第2层子组件,在数据更新时无法正确获取props并更新

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

  1. 正常显示

WX20190920-115829@2x

  1. 点击界面上的 refresh 按钮
  2. 错误显示

WX20190920-115853@2x

pages/index

import Taro, { Component, Config } from '@tarojs/taro'
import { View, Text, Button } from '@tarojs/components'
import './index.css'

import InfoList from '../../components/info-list';

let key = 0;

export default class Index extends Component {
  config: Config = {
    navigationBarTitleText: '首页'
  }

  state: {
    items: any[]
  } = {
    items: []
  }

  componentDidMount () {
    this.refresh();
  }

  refresh = () => {
    console.log('refresh');
    let count = 2;
    let newItems: any[] = [];
    for (let i = 0; i < count; i++ ){
      ++key;
      newItems.push({
        key: 'id-' + key,
        text: 'text-' + key
      })
    }
    console.log(newItems)

    this.setState({
      items: newItems
    })
  }

  render () {
    const { items } = this.state;
    console.log(items)

    return (
      <View className='index'>
        <Text>Hello world!</Text>
        <InfoList items={items} />
        <Text>Hello world! AAA</Text>

        <Button onClick={this.refresh}>refresh</Button>
      </View>
    )
  }
}

components/info-list

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

import Info from '../info';

export default class InfoList extends Component<any> {
  render () {
    const { items } = this.props;

    console.log('info-list', items);

    return (
      <View>
        <View>Info List</View>
        {
          items.map((item) => {
            const itemKey = item.key;
            const itemText = item.text;
            const itemData = item;

            return (
              <View key={itemKey}>
                <Info
                  key={itemKey}
                  item={itemData}
                  text={itemText}
                  renderMore={(
                    <View>{itemKey}</View>
                  )}
                ><View>children-{itemKey}</View></Info>
              </View>
            )
          })
        }
        <View>Info List End</View>
      </View>
    )
  }
}

components/info

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

// import Box from '../box';
export default function Info (props) {
  const { item } = props;

  console.log('info item', item);

  const computeValue = item ? 'computeValue' + item.text + item.key : 'no-item'

  return (
    <View>
      <View>{props.renderMore}</View>
      <View>{props.children}</View>
      <View>{computeValue}</View>
    </View>
  )
}

期望行为
refresh 后也能正确显示

报错信息

系统信息
👽 Taro v1.3.15

Taro CLI 1.3.15 environment info:
System:
OS: macOS 10.14.6
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 8.9.3 - ~/.nvm/versions/node/v8.9.3/bin/node
Yarn: 1.17.3 - ~/.nvm/versions/node/v8.9.3/bin/yarn
npm: 6.1.0 - ~/.nvm/versions/node/v8.9.3/bin/npm
npmPackages:
@tarojs/components: 1.3.18 => 1.3.18
@tarojs/plugin-babel: 1.3.18 => 1.3.18
@tarojs/plugin-csso: 1.3.18 => 1.3.18
@tarojs/plugin-uglifyjs: 1.3.18 => 1.3.18
@tarojs/router: 1.3.18 => 1.3.18
@tarojs/taro: 1.3.18 => 1.3.18
@tarojs/taro-alipay: 1.3.18 => 1.3.18
@tarojs/taro-h5: 1.3.18 => 1.3.18
@tarojs/taro-qq: 1.3.18 => 1.3.18
@tarojs/taro-quickapp: 1.3.18 => 1.3.18
@tarojs/taro-swan: 1.3.18 => 1.3.18
@tarojs/taro-tt: 1.3.18 => 1.3.18
@tarojs/taro-weapp: 1.3.18 => 1.3.18
@tarojs/webpack-runner: 1.3.18 => 1.3.18
eslint-config-taro: 1.3.18 => 1.3.18
eslint-plugin-taro: 1.3.18 => 1.3.18
nerv-devtools: ^1.4.0 => 1.4.4
nervjs: ^1.4.0 => 1.4.4
stylelint-config-taro-rn: 1.3.18 => 1.3.18
stylelint-taro-rn: 1.3.18 => 1.3.18

补充信息
WX20190920-120115@2x
可以看到更新了4次(数组里有2个元素,所以都更新了2次)

2次init, key变化导致了init的发生
WX20190920-120308@2x
WX20190920-120324@2x

2次 update
WX20190920-120352@2x
WX20190920-120413@2x
根据断点猜测 很可能 update和init 操作到都是不同的组件,导致正确都数据没有更新到新挂载到组件上

@Chen-jj
Copy link
Contributor

Chen-jj commented Sep 23, 2019

@ignous CLI 和依赖版本保持一致。

@huey-LS
Copy link
Contributor Author

huey-LS commented Sep 23, 2019

更新了, 但是问题还是存在
WX20190923-103032@2x

@taro-bot
Copy link

taro-bot bot commented Sep 23, 2019

CC @Chen-jj

@Chen-jj
Copy link
Contributor

Chen-jj commented Oct 11, 2019

@ignous components/info-list 中的 View 组件不要加 key。taro 的 props 系统在这种情况下会和微信的 diff 算法有冲突。

@Chen-jj Chen-jj added answered question Further information is requested labels Oct 11, 2019
@taro-bot
Copy link

taro-bot bot commented Oct 11, 2019

Hello~

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

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

Good luck and happy coding~

@huey-LS
Copy link
Contributor Author

huey-LS commented Oct 12, 2019

image
这里应该是 compid生成规则里没有使用key,单纯的使用index导致的吧,index和key不同步就会有问题吧

@huey-LS
Copy link
Contributor Author

huey-LS commented Oct 12, 2019

anonIdx换成 key就正常了, 可能需要转换的时候做更多的判断吧

@Chen-jj
Copy link
Contributor

Chen-jj commented Oct 12, 2019

@ignous 这么说吧,这是歪打正着的结果。

初次渲染

genCompid 为自定义组件生成组件 id,假设 1,2。然后把 props 放进 propsManager。

propsManager:

{
  '1': {
    key: 'id-1'
  },
  '2': {
    key: 'id-2'
  }
}

点击按钮

点击按钮后会跑父组件的 render 逻辑,重新 genCompid 生成组件 id,还是 1 和 2。然后把 props 放进 propsManager。

propsManager:

{
  '1': {
    key: 'id-3'
  },
  '2': {
    key: 'id-4'
  }
}

因为加了 key,会先卸载两个 info 组件,再加入两个。

错误原因:info 组件 unmount 时会把 propsManager 中对应的 props 对象销毁。

propsManager:

{}

新创建的两个 info 组件再到 propsManager 中取 props 时,对应的 props 已经被销毁,所以props 为空。

不加 key

不加key,diff 算法不会销毁 info,走组件更新逻辑,因此没有问题。

anonIdx 换成 key

在点击按钮时,key 是用户逻辑,会自增,genCompid 会生成 3 和 4。

{
  '1': {
    key: 'id-1'
  },
  '2': {
    key: 'id-2'
  },
  '3': {
    key: 'id-3'
  },
  '4': {
    key: 'id-4'
  }
}

然后 propsManager[1]、propsManager[2] 即使被删除,还是不影响获取 props。

但这样会导致内存泄漏。

@Chen-jj Chen-jj closed this as completed Oct 12, 2019
@huey-LS
Copy link
Contributor Author

huey-LS commented Oct 12, 2019

  1. 卸载,再重新创建2个新的子组件我觉得是个挺常见的操作,特别是处理子组件没有多的处理props update,那种功能复杂,update里处理不到位的子组件。

  2. 内存溢出产生原因不是很明白,compid生成规则从index到key后, key=1,key=2组件unmount的时候为什么无法清理对应propsManager中的数据, 感觉处理应该是类似的? 这里逻辑我还没看过不是很确定

主要是感觉这个问题,使用map得建议不加key了... 这个感觉和之前用react的用法推荐正好相反呀

@huey-LS
Copy link
Contributor Author

huey-LS commented Oct 12, 2019

当然不是我上面说明的那个直接index换key的写法哈。。 只是在想compid 的规则调整下,是不是能解决这个问题,如果能愉快的使用key,代码写起来会更舒服哈

@huey-LS
Copy link
Contributor Author

huey-LS commented Oct 12, 2019

因为加了 key,会先卸载两个 info 组件,再加入两个。

错误原因:info 组件 unmount 时会把 propsManager 中对应的 props 对象销毁。

话说这个原因我很认同,我的初步推测也是卸载然后新增导致的

但是为啥是建议去掉key呀...
这个建议不能理解呀,我本来想是不是是能在 编译时 compid的生成规则就会考虑react key的因素,让propsMange里的数据和实际生成的组件保证一致

** 内存溢出的说法 **
image
我也是实际测试了,如图并不会,unmount很好的卸载了数据

当然我改的也比较粗暴,直接把anidx改成了key

我在想 是不是有其他原因不能key
比如 其他小程序端对 key的处理不一样(其他小程序我不太熟悉,微信小程序是有key的)。希望能被告知

Chen-jj added a commit that referenced this issue Oct 14, 2019
对于循环,先删除 PropsManager 中旧 compid 对应的 props,再生成新 compid 对应的 props。
目的是绕过微信小程序加 key 后 diff 算法某些情况时会先卸载组件再加入组件,以致卸载组件时把新 props 清理掉。#4497
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
3 participants