React:更新子组件而不重新渲染父组件

IT技术 javascript reactjs
2021-05-02 15:18:40

下面是一个简单的例子:

const { Component } = React
const { render } = ReactDOM

const Label = ({ text }) => (
  <p>{text}</p>
)

const Clock = ({ date }) => (
  <div>{date.toLocaleTimeString()}</div>
)

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      date: new Date()
    }
  }
  
  componentDidMount() {
    this.interval = setInterval(
      () => this.setState({ date: new Date() }),
      1000
    )
  }
  
  componentWillUnmount() {
    clearInterval(this.interval)
  }
  
  updateTime() {
    
  }
  
  render() {
    return (
      <div>
        <Label text="The current time is:" />
        <Clock date={this.state.date} />
      </div>
    )
  }
  
}

render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="app"></div>

this.setState({ date: new Date() })每秒被调用一次,用当前时间更新时钟。据我所知,setState在 App 上调用 render 方法会导致整个组件重新渲染,包括标签。

有没有办法将日期传递给时钟(导致它被重新渲染)而不重新渲染整个 App 组件?这对性能有多大作用?

2个回答

当你想在不更新父级的情况下更新子级时,状态必须在子级中。您可以将状态 getter / setter 从子级传递给父级,以便能够读取和更新它:

function Child({onMount}) {
  const [value, setValue] = useState(0);

  useEffect(() => {
    onMount([value, setValue]);
  }, [onMount, value]);


  return (
    <div>
      {value}
    </div>    
  );
};


function Parent() {

  let value = null;
  let setValue = null;
  
  const onChildMount = (dataFromChild) => {
    value = dataFromChild[0];
    setValue = dataFromChild[1];
  };

  // Call setValue to update child without updating parent

  return (
    <div>
      <Child onMount={onChildMount}/>
    </div>    
  );
};

由于const [value, setValue] = useState(0);在 中Child,更新值时只有子组件会重新渲染。此外,由于Parent接收valuesetValueat onChildMount,父级可以使用它们来更新子级而无需重新渲染父级。

你想要的东西是不可能的。要将 prop 传递给子组件,父组件的 state 或 props 应该以某种方式改变。如您所知,这显然会触发重新渲染,因此所有子项都重新渲染。要更新,您的Clock组件应该在这种情况下重新渲染和卸载/重新安装以反映 DOM 更改。

如果您的应用程序不是那么大,并且没有那么多孩子,那么不要为这个问题而烦恼,因为渲染并不是那么昂贵。代价高昂的是对组件的 DOM 操作。在这里,React 区分真实和虚拟 DOM,Label即使重新渲染也不会卸载/重新安装组件。但是,如果您将Label组件编写为 aPureComponent它不会重新渲染。但是要Clock像这样更新组件,就没有办法了。

class Label extends React.PureComponent {
  render() {
    console.log("rendered");
    return (<p>{this.props.text}</p>)
  }
}

const Clock = ({ date }) => (
  <div>{date.toLocaleTimeString()}</div>
)

class App extends React.Component {

  constructor() {
    super()
    this.state = {
      date: new Date()
    }
  }


  componentWillMount() {
    this.interval = setInterval(
      () => this.setState({ date: new Date() }),
      1000
    )
  }

  componentWillUnmount() {
    clearInterval(this.interval)
  }

  updateTime() {

  }

  render() {
    return (
      <div>
        <Label text="The current time is:" />
        <Clock date={this.state.date} />
      </div>
    )
  }

}