iThink icon indicating copy to clipboard operation
iThink copied to clipboard

为什么我的this.props.children不能re-render?

Open hufeng opened this issue 9 years ago • 1 comments

困于心的原因常常没有对事物本质有个清晰的认知,导致认识上的偏差

Container Component VS Pure Component

React与很多view层框架一个很不同的地方就是强调数据的单项数据流动。这点也是iflux在设计的时候特别强调的,针对这种单向的数据流动,React的组件就变成两种 Container Component 和 Pure Component。

Why Container Component

理论上在React组件中,Pure Component越多越好,带来更好的组件重用。Container Component主要可以帮助我们更好的分离关注点,例如:

render() {
  if (this.props.isLoading) {
    return <QMLoaing/>
  } else {
    return <QMUserProfile/>
  }
}

在app中可能很多的地方都在使用QMLoading来完成页面的loading效果,每个组件中这样写就很不优雅,大量的重复代码,而且整个组件的组合都不够声明式。

这样时候我们就可以很好的使用Container Component去重构我们的应用,首先抽象一个LoadingContainer.

class LoadingContainer extends React.Component {
  render() {
    if (this.props.isLoading) {
      return <QMLoading/>
    } else {
      return <View>{ this.props.children }</View>
    }
  }
}

how to use?

import LoadingContainer from 'LoadingContainer';

class User extends React.Component {
  render() {
    return (
      <LoadingContainer isLoading={ true }>
        <UserProfile/>
      </LoadingContainer>
    );
  }
}

是不是优雅很多?:)

但是这个里面有一个很容易忽略的一个问题,这里面的UserProfile的渲染并不受LoadingContainer来控制,而是User。比如如果LoadingContainer里面状态的更新引起re-render,但是这个re-render并不会引起

<UserProfile/>

的re-render,只有User去re-render才会去引起UserProfile的更新。

例如:

class HelloContainer extends React.Component {
  componentDidMount() {
    setTimeout(() => {
      this.forceUpdate();
    }, 2000);
  }


  render() {
    return <div> { this.props.children } </div>;
  }
}


class Hello extends React.Component {
  render() {
    return <div>{ this.props.random } </div>;
  }
}


class App extends React.Component {
  render() {
    return (
      <HelloContainer>
        <Hello name={Math.random()}/>
      </HelloContainer>
    );
  }
}

这里面就是当HelloContainer去forceUpdate的时候,并不引起this.props.children去更新。

这是为什么呢? 直觉的方式去HelloContainer中的render方法console.log下this.props.children到底是什么?

结果却是打印出一个对象,是一个对象,为什么是一个对象?到底this.props.cchildren代表什么,我们做一些小的变动。

class HelloContainer extends React.Component {
  componentDidMount() {
    setTimeout(() => {
      this.forceUpdate();
    }, 2000);
  }


  render() {
    return <div> { this.props.children() } </div>;
  }
}


class Hello extends React.Component {
  render() {
    return <div>{ this.props.random } </div>;
  }
}


class App extends React.Component {
  render() {
    return (
      <HelloContainer>
        { () => <Hello name={Math.random()}/> }
      </HelloContainer>
    );
  }
}

这回正如我们所料,当HelloContainer更新的时候,Hello组件也更新了。

这次我们看的比较清楚了,this.props.children就是

{ () => <Hello name={Math.random()}/> }

那上面的例子,this.props.children是什么?是

<Hello name={Math.random()}/>

Oh, NO! 当然不是,如果是打印出来应该是一个函数。也许你已经猜到了是这个组件对于的render后的对象。为什么?

这里面容易被JSX迷惑,Hello只是React.createElement优雅表象形式。

因为React在渲染的时候,先渲染子节点,在渲染父节点。这里就先渲染Hello, 渲染Hello本质上就是去调用了下React.createElement然后返回virtualdom对象,

这个对象就是HelloContainer中this.props.children对象, 所有当HelloContainer去forceUpdate时候这个对象不会去re-render。

原来就是这样!

hufeng avatar Feb 20 '16 14:02 hufeng

大师威武~

gxthrj avatar Feb 23 '16 02:02 gxthrj