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

feat: getContainer改变后更新Portal的容器 #89

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

binyellow
Copy link

  1. 修复的问题现象是:当我改变容器后,visible变化第一次不会渲染到对的container中,当visible第二次变为true才会渲染正确
  2. 问题的原因是:当getContainer改变后,内部createPortal的container一直没变,和这个类似#17645
  3. removeChild的节点的引用会被存起来,因为引用指向父组件的this.container,但是该节点和父子节点的关联关系被删除掉了,导致下次渲染在PortalWrapper中判断存在,不会重新createElement,所以我在unMount中将container置为null了。参考

@lgtm-com
Copy link
Contributor

lgtm-com bot commented Mar 22, 2020

This pull request introduces 1 alert when merging fe8155a into 9453a84 - view on LGTM.com

new alerts:

  • 1 for Syntax error

@coveralls
Copy link

coveralls commented Mar 22, 2020

Coverage Status

Coverage increased (+10.3%) to 35.036% when pulling e7e5b46 on binyellow:master into 9453a84 on react-component:master.

src/Portal.js Outdated
@@ -25,19 +29,23 @@ export default class Portal extends React.Component {
}

createContainer() {
this._container = this.props.getContainer();
this.portalContainer = this.props.getContainer();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不建议换名字,我隐约记得换名字是个坑

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改这个是因为eslint报错,和其他组件之间有关联是吧?

src/Portal.js Outdated
if (this._container) {
this._container.parentNode.removeChild(this._container);
if (this.portalContainer) {
this.portalContainer = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 null 没看懂,为啥要改,如果是 container 变了的原因多加个判断就行

{() => 'Content'}
</PortalWrapper>,
);
expect(wrapper).toMatchSnapshot();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个测试有点问题,snapshot 没看到。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package.json Outdated
@@ -44,6 +44,7 @@
"create-react-class": "^15.6.3",
"enzyme": "^3.10.0",
"eslint": "^6.6.0",
"eslint-plugin-react-hooks": "^2.5.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是yarn test的时候要装的,我去掉下

getContainer: document.getElementById('dom1'),
});
const wrapperDom1 = portal.parent();
expect(oldWrapper !== wrapperDom1).toEqual(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有点点小问题,mount 返回的是个 wrap 对象,你这个等于 if( {} !== {} ),可以这样写测试

describe('container', () => {
  let div1
  let div2
  beforeAll(() => {
    div1 = document.createElement('div');
    div1.id = 'dom1';
    div2 = document.createElement('div');
    div2.id = 'dom2';
    document.body.appendChild(div1);
    document.body.appendChild(div2);
  });
  afterAll(() => {
    document.body.removeChild(div1);
    document.body.removeChild(div2);
  })
  it('Same function returns different DOM', async () => {
    const wrapper = mount(
      <PortalWrapper
        visible
        getContainer={() => document.getElementById('dom1')}
      >
        {() => <div id="children">Content</div>}
      </PortalWrapper>,
    );

    expect(document.querySelector('#dom1 #children')).not.toBeNull()

    wrapper.setProps({ getContainer: () => document.getElementById('dom2') });

    expect(document.querySelector('#dom1 #children')).toBeNull()
    expect(document.querySelector('#dom2 #children')).not.toBeNull()
  });
});

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

恩 去antd里面也找了例子,没找到类似测wrapper得,本来这里用contains,发现可能是因为position为absolute,导致一直返回false,就不知道咋写,谢谢学习了

src/Portal.js Outdated
Comment on lines 44 to 47
const currentContainer = this.props.getContainer();
if (currentContainer && this._container !== currentContainer) {
this._container = currentContainer;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个最好可以放到 lifeCycle 里面去,参考 wrapper,就这样吧

Copy link
Member

@shaodahong shaodahong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, @zombieJ Please help confirm, thanks

@zombieJ
Copy link
Member

zombieJ commented Mar 27, 2020

这么一改每次 render 都要更新一下。比较好奇什么需求会需要动态改容器?

@binyellow
Copy link
Author

这么一改每次 render 都要更新一下。比较好奇什么需求会需要动态改容器?

我们有个需求是要根据用户的配置去改变抽屉的容器。只会在container变化才会更新this._container

@zombieJ
Copy link
Member

zombieJ commented Mar 30, 2020

个人是不太想支持这个更新,对于所有依赖了它的组件都会有性能损耗。而得到的好处也并不太多。从业务角度也是可以通过全刷新来实现更新容器的。

你们什么想法? @afc163 @shaodahong @AshoneA

@AshoneA
Copy link

AshoneA commented Mar 31, 2020

感觉可以把 container 放到组件内部解决? https://codesandbox.io/s/anniuleixing-ant-design-demo-v7dtx

我觉得用外部的一个 mutable 的变量来决定 React 视图其实不太好,毕竟 React 更加推崇用 v = f(d) 来决定视图。如果实在需要依赖外部用户配置,我觉得业务需求上应该想想怎么在用户配置改变的时候去更新下组件内的状态,因为如果你业务上组件依赖外部 mutable 变量(用户配置),估计其他地方也会碰到一些 React 视图不更新的问题。

@@ -37,6 +37,10 @@ export default class Portal extends React.Component {

render() {
if (this._container) {
const currentContainer = this.props.getContainer();
if (currentContainer && this._container !== currentContainer) {
this._container = currentContainer;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this._container = currentContainer;
this.removeContainer();
this._container = currentContainer;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果要这么写的话,应该要把上次的结点移除掉,不然其实会有 bug.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

恩 是的,测到了。
关于更新的问题,其实PortalWrapper里面也做了相应的处理,在getDerivedStateFromProps中做了判断和移除操作,只不过Portal内没更新,Portal不做更新复用之前的container在性能上确实会好点,看你们判断吧

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

Successfully merging this pull request may close these issues.

None yet

5 participants