【问题标题】:Re-render React descendant components without re-rendering ancestors重新渲染 React 后代组件而不重新渲染祖先
【发布时间】:2019-08-21 06:21:56
【问题描述】:

我正在尝试使用 React 16.8.3 构建导航栏。我想使用组合来传递导航栏内容,而不是通过道具传递配置对象,以获得更大的灵活性。像这样的:

<Navbar>
    <NavItem>Some label</NavItem>
    <NavItem>
        <span>Some arbitrary content</span>
    <NavItem>
</Navbar>

代替:

const navItems = [
  {
    label: 'Some label'
  },
  {
    label: 'Some other label'
  }
]

<Navbar items={navItems} />

到目前为止,导航栏工作正常。我在 shouldComponentUpdate 方法中添加了一些逻辑来防止多次重新渲染:

shouldComponentUpdate(nextProps) {
   return nextProps.selectedItem !== this.props.selectedItem;
}

因此,导航栏仅在其所选项目更改时重新渲染,而不是在导航栏父级重新渲染时。

问题是一个 NavItem 包含一个带有任务计数的标记,每当用户执行某些任务时必须更新该标记:

Todos screenshot

项目标记是:

<Navbar>
    <NavItem>
        <div className="has-badge">
           <span>Label</span>
           <span className="badge">{this.props.toDoCount}</span>
        </div>
    </NavItem>
</Navbar>

this.props.toDoCount 是导航栏父级的道具,而不是导航栏本身的道具。

如何在不重新渲染整个导航栏的情况下更新徽章编号?到目前为止,我已经尝试创建一个徽章组件,添加一些状态,以及使用导航栏父级中的 ref 更新徽章编号的方法:

import React, { PureComponent } from 'react';

interface BadgeProps {
  number: number;
}

class Badge extends PureComponent<BadgeProps> {
  state = {
    number: 0
  };

  setCount(number) {
    this.setState({
      number
    });
  }

  render() {
    return <span className="badge">{this.state.number}</span>;
  }
}

在导航栏父级中:

private todos = createRef<Badge>();

...

componentDidUpdate(prevProps: EhrProps) {    
   this.todos.current.setCount(toDosCount);
}

它正在工作,但是......在 React 中是否有更简单或更清洁的方法??

谢谢!

PS:我们在项目中使用 Redux,但我想避免使用 Navbar 中的 store 或其项目。

编辑:

我在导航栏的渲染方法中使用了 React.children 和 React.cloneElement:

render() {
    const { className, children, selectedItem, ...rest } = this.props;
    const classes = classNames(
      {
        navbar: true
      },
      className
    );

    return (
      <nav className={classes} {...rest}>
        {React.Children.map(children, child => {
          if (child.type === NavItem) {
            return React.cloneElement(child, {
              onClick: this.handleItemClick,
              selected: child.props.name === selectedItem
            });
          }
          return child;
        })}
      </nav>
    );
  }

每个 NavItem 处理自己的渲染:

return (
    <div className={classes} onClick={handleClick} onKeyPress={handleKeyPress} role="menuitem" tabIndex={0}>
      {children}
    </div>
);

【问题讨论】:

    标签: reactjs


    【解决方案1】:

    大概你有一些Navbar 组件的代码,看起来有点像这样。

    class Navbar extends React.Component<Props> {
      render() {
        return (
          <div>
            {this.props.navItem.map(item => <NavItem key={item.label}>{item.label}</NavItem>)};
          </div>
        );
      }
    }
    

    然后是一些代码来呈现每个孩子NavItem

    为了使组件相当高效,重新渲染整个 Navbar 就足够了,而不是重新渲染每个子项。

    我推荐的是:

    1. 使导航栏的每个子项都在其自己的组件中呈现;在上面的组件中它被称为NavItem
    2. 使用componentShouldUpdateReact.PureComponent(看看这个!一旦你理解它,这是一个很好的通用解决方案,默认情况下使用每个组件而不是 React.Component)来确保每个子组件只重新渲染当它的值发生变化时

    当您更新单个 NavItem 的标记时,Navbar 将重新渲染。大多数NavItems 会看到他们的道具没有改变,也不会重新渲染。 Navbar 的拥有徽章的单个孩子将发生变化,并将重新渲染。有了这个,实际开销实际上是很低的。

    如果您的Navbar 有很多孩子,或者您的该单身孩子的徽章发生了很大变化,您可能可以通过使用React.Context 或 Redux 为该单身孩子传递值来进一步优化它,但感觉杂乱无章,似乎是过早的优化。

    祝你好运!

    【讨论】:

    • 实际上,我在导航栏的渲染方法中使用了 React.children 和 React.cloneElement(参见编辑),因此使用 props.children 渲染内容。
    猜你喜欢
    • 1970-01-01
    • 2021-06-19
    • 2018-04-22
    • 1970-01-01
    • 2021-06-07
    • 2018-08-12
    • 2021-04-28
    • 2022-01-27
    相关资源
    最近更新 更多