返回 登录
4

巧用React Fiber中的渲染字符串新功能

阅读27202

虽然React Fiber还没有正式发布,但是我们已经可以预先领教其带来的新的编程模式了。

在React Fiber中,render函数可以直接返回一个字符串了,换言之,一个组件可以直接渲染为一个字符串,而不是必须渲染为一个HTML模样的物体。

举个例子,下面这个控件LongString,显示一个input和一个p,p中文字可以是很长的字符串,相当于一个模板,在input中输入的字符串会用来填补p中的模板。

图片描述

代码如下。

import React from 'react';


class LongString extends React.Component {
  constructor() {
    super(...arguments);

    this.onInputChange = this.onInputChange.bind(this);
    this.state = {str: ''};
  }

  onInputChange(e) {
    this.setState({
      str: e.target.value
    });
  }

  render() {
    console.log('enter render');
    return <div>
      <input onChange={this.onInputChange} />
      <p>
       让我们假装这是一段超长的字符串,包含 {this.state.str} 这样的子串,而且包含多个{this.state.str}.
      </p>
    </div>;
  }
}

上面组件的工作原理是通过事件处理函数onInputChange来更新组件的state,引发组件重新渲染,这样this.state.str才能在渲染过程中被显示。

上面的组件工作完全正确,但是有个问题,就是每一次在input中更新内容,都会引发LongString的更新过程,在浏览器的console中,可以看到render函数被反复调用的痕迹。

图片描述

想想看,其实LongString组件渲染了很长的字符串,每次更新的只有一小部分,却依然走整个渲染过程,实在有那么一点点浪费,有没有更好的办法呢?

在以前,也有办法,就是把更新的子串让另一个子组件来渲染,可以做到只更新那一个子组件,但是子组件必须把更新的子串放在某个HTML元素中,比如,这样就污染了LongString渲染的长字符串。假如input中是hello,产生的HTML就像下面这样。

      <p>
       让我们假装这是一段超长的字符串,包含<span>Hello</span>这样的子串,而且包含多个<span>Hello</span>.
      </p>

而我们实际想要的是这样。

      <p>
       让我们假装这是一段超长的字符串,包含Hello这样的子串,而且包含多个Hello.
      </p>

有了React Fiber之后,一个组件可以直接返回一个字符串了,这样我们就可以既保持字符串局部更新,又避免被HTML标签污染。

代码如下。

import React from 'react';
const EventstateUpdater = require('events');

class LongStringChunked extends React.Component {
  constructor() {
    super(...arguments);

    this.onInputChange = this.onInputChange.bind(this);
    this.state = {str: ''};
    this.stateUpdater = new EventstateUpdater();
  }

  onInputChange(e) {
    this.stateUpdater.emit('update', e.target.value);
  }

  render() {
    console.log('enter render');
    return <div>
      <input onChange={this.onInputChange} />
      <p>
        让我们假装这是一段超长的字符串,包含 <Chunk listen={this.stateUpdater} />  这样的子串,而且包含多个<Chunk listen={this.stateUpdater} />.
      </p>
    </div>;
  }
}

class Chunk extends React.Component {
  constructor(props) {
    super(...arguments);

    this.state = {str: ''};
  }

  componentDidMount() {
    this.props.listen.on(
      'update',
      str  => {
        this.setState({str: str})
      }
    );
  }

  render() {
    console.log('enter chunk render');
    return this.state.str;
  }
}

我们用LongStringChunked代替LongChunk,在render函数中用Chunk这个组件实例代替this.state.str,传递给Chunk的listen这个prop是一个EventEmitter,当input变化的时候,通过这个EventEmitter发出一个信号让Chunk去更新自己,而不是让LongStringChunked重新绘制。

<Chunk listen={this.stateUpdater} /> 

重新尝试在input里写点啥,在浏览器console中可以看到,LongStringChunked的render函数没有被反复调用,只有Chunk组件被反复渲染,因为Chunk组件渲染的内容要比LongStringChunked少得多,所以(理论上)要节省得多。

图片描述

对于LongStringChunked的render函数,既然多个Chunk实例都是一样的,可以这样写显得漂亮点。

  render() {
    console.log('enter render');
    const chunk = <Chunk listen={this.stateUpdater} />;
    return <div>
      <input onChange={this.onInputChange} />
      <p>
        让我们假装这是一段超长的字符串,包含{chunk}这样的子串,而且包含多个{chunk}.
      </p>
    </div>;
  }

当然,你要问,这种优化真的能节约多少?我只能说看具体情况。

这只是展示React Fiber引入的一个新功能,并不表示你必须在实际工作中应用,实际上,只有在性能真的很成问题的时候,才需要去做这样的优化,不过,了解一点新技巧没什么坏处,对吧。

我们知道有这样一个武器,但是不一定要去用他,我们手里握着核弹,一样也不一定要使用它。

让我们一起期待React Fiber正式发布吧!

作者:程墨,资深架构师,曾任职于摩托罗拉、雅虎和微软,云鸟配送平台联合创始人,目前服务于美国视频服务公司Hulu。

《深入浅出React和Redux》作者,此书由浅入深介绍如何用React和Redux构建前端项目,产出高质量易维护代码。

图片描述

评论