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

21

下面是一个简单的示例:

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组件的渲染方法,导致整个组件(包括Label)重新渲染。

是否有一种方法可以将日期传递给时钟(使其重新呈现),而不必重新呈现整个应用程序组件?这对性能有多大影响?


2
将时钟组件赋予自己的内部状态,而不是将其作为父组件的属性传递。 - jmargolisvt
1
渲染函数被调用和实际更改DOM之间存在差异。React的整个重点是将其呈现为虚拟DOM,然后将其与之前的内容进行比较,并仅更新所需内容。这称为协调。在您的示例中,Label不会重新呈现,因为它没有更改,但Clock将会重新呈现。 - Chris Cousins
1
不,如果父组件重新渲染,则默认情况下所有子组件都重新渲染。@ChrisCousins可能您想表达的是Clock组件中不需要进行DOM操作。 - devserkan
如果您希望停止特定组件的重新渲染,可以使用组件生命周期方法shouldComponentUpdate - Isaac
DOM中的Label元素不会被修改 - 它将呈现其虚拟DOM,但在实际的DOM中它不会被改变。 - Chris Cousins
显示剩余2条评论
4个回答

20

如果你想要更新子组件而不更新父组件,那么状态必须在子组件中。你可以将子组件的状态 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组件中,更新值时只会重新渲染子组件。此外,由于ParentonChildMount接收到valuesetValue,父组件可以使用它们来更新子组件而无需重新渲染父组件。


1
谢谢,这个完美地运行了。我唯一做的更改是不需要将 value 传递给父组件,所以 onMount(setValue); 就足够了。 - Lewy Blue
这是一个好的实践吗? - Max Hay

8
无法实现您想要的功能。要将prop传递给子组件,父组件的状态或props应该以某种方式发生变化。正如您所知,这显然会触发重新渲染,因此所有子组件都会重新渲染。为了更新,您的组件在此情况下应重新渲染并卸载/重新挂载以反映DOM更改。
如果您的应用程序不是很大,并且没有太多的子元素,则不要为此问题而苦苦挣扎,因为渲染并不那么昂贵。昂贵的是组件的DOM操作。在这里,React对比真实和虚拟DOM,即使它重新渲染,也不会卸载/重新挂载

有没有办法避免通过父级进行传播?我有高度嵌套的结构(SVG),我知道只有某些子元素会受到状态变化的影响。目前,由于钻取而导致可见延迟。 - Nikunj Madhogaria
@NikunjMadhogaria,如果我理解正确的话,您正在寻找React.PureComponent - devserkan
我们有像这样的普通CSS规则 body[x="1"] #id1 = {...style 1}, body[x="2"] #id1 = {...style 2}。目前这比在React中使用递归方法更快。 - Nikunj Madhogaria
2
性能是我们应用程序的重要指标之一。我们有两个解决方案:第一个是纯CSS,没有任何JavaScript,第二个是使用React。如果纯CSS需要200ms才能更新,那么对于同样的更新,React需要2000ms(因为它在JavaScript中有额外的计算)。 - Nikunj Madhogaria
你不应该还在使用那种编程范式。转换到钩子并使用“memo”。 - Adrian Bartholomew
显示剩余5条评论

1
使用子组件的引用可以实现以下目标:
export const parentA = () => {
  let ref = useRef<View>(null);
  const updateChild () {
    ref.current.props.setNativeProps({style:{backgroundColor: 'gray',}})
  }
  return (
    <childA>
      <Text>Test</Text>
    </childA>
  );
};

export const childA = () => {
  return <View style={{backgroundColor: 'green',}}></View>;
};

1
使用useImperativeHandleReact.forwardRef,你可以将状态从子组件返回给父组件作为引用(ref)。
function Parent() {
  const childRef = useRef(null);
  
  function printValue() {
    console.log(childRef.current.value);
  }

  function setValueToOne() {
    childRef.current.setValue(1);
  }

  return (
    <div>
      <Child ref={childRef} style={{ color: "red" }} />
    </div>
  );
}

const Child = forwardRef(function Child({ style }, ref), () => {
  const [value, setValue] = useState(null);

  useImperativeHandle(ref, () => {
    return {
      value,
      setValue,
    }   
  }, [value, setValue]);

  return <div style={style}>{value}</div>
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接