Help
Support Us
You are viewing the documentation for an older version of Preact. Get started with the current version.

关联状态

在优化 state 改变的方面,Preact 比 React 走得跟超前一点。在 ES2015 React 代码中,通常的模式是在 render() 方法中使用箭头函数,以便于响应事件,更新状态。每次渲染都在局部创建一个函数闭包,这样效率十分低下而且会迫使垃圾回收器做许多不必要的工作。



更好的手动处理方式

一个办法是使用 ES7 的类属性声明绑定了组件的方法。(类实例域):

class Foo extends Component {
    updateText = e => {
        this.setState({ text: e.target.value });
    };
    render({ }, { text }) {
        return <input value={text} onInput={this.updateText} />;
    }
}

尽管这样做获得了更优异的运行性能,但是它还是需要编写许多不必要的代码来关联 state 和 UI。

另一个解决方案是使用 ES7 的装饰器,声明式地绑定组件方法。例如 decko's @bind

让关联状态(Linked State)来拯救你

幸运的是,这有一种使用Preact的 linkState 模块的解决方案。

Preact之前的版本内置了 linkState() 方法; 但是,这个方法已经被移除出去成了单独的模块。 如果你想要使用以前的代码编写方式, 请查看 这个页面 获取更多关于使用polyfill的信息。

当发生一个事件时,调用 linkState(this, 'text') 将会返回一个处理器函数,这个函数把它相关的值更新到你组件状态内指定的值。 多次使用相同的 component and name 参数调用 linkState(component, name) 时,结果会被缓存起来。所以就必然不存在性能问题。 以下代码就是之前的样例通过 Linked State 的重写:

import linkState from 'linkstate';

class Foo extends Component {
    render({ }, { text }) {
        return <input value={text} onInput={linkState(this, 'text')} />;
    }
}

这段代码简洁明了,易于理解且高效。他能够处理来自任何输入形式的关联状态。 可选的第三个参数 'path' 能够显式提供一个点式路径(形如:foo.bar.baz)给新的状态,用于自定义绑定(如绑定到第三方组件的值)。

Custom Event Paths

默认情况下 linkState() 会试图从事件中推导出合适的值。 例如,一个 <input> 元素会将给定的state的属性的值设置为 event.target.value 或者 event.target.checked 这取决于 input 的 type 属性值。 对于自定义事件处理器,将纯粹的值传递给 linkState() 生成的事件处理函数,这个处理器将使用这个值。在大多数情况下,这种方法是可行的。

但是,有些情况下这是不可行的 —— 自定义事件和分组单选按钮就是这样的两个例子。在这些情况下,可以给 linkState() 传递第三个参数去指定可以找到值的事件点式路径(如event.type)。

为了理解这项特性,查看 linkState() 背后实现的秘密是非常有用的。以下展示了手动创建一个事件处理器,并且将事件对象的值传递给state。这在功能上等同于 使用 linkState() 的方式,但并不包括使 linkState() 有存在价值的缓存性能优化。

// linkState返回的事件处理器:
handler = linkState(this, 'thing', 'foo.bar');

// ...在功能上等同于:
handler = event => {
  this.setState({
    thing: event.foo.bar
  });
}

例子:分组单选按钮

下面的程序不会像预期般运行。如果用户点击了 "no", noChecked 变成了 true ,但是 yesChecked 仍然是 true, 因为另一个按钮的 onChange 事件并没有被触发:

import linkState from 'linkstate';

class Foo extends Component {
  render({ }, { yes, no }) {
    return (
      <div>
        <input type="radio" name="demo"
          value="yes" checked={yes}
          onChange={linkState(this, 'yes')}
        />
        <input type="radio" name="demo"
          value="no" checked={no}
          onChange={linkState(this, 'no')}
        />
      </div>
    );
  }
}

linkState 的第三个参数在这里会派上用场。它允许你提供事件对象的点式路径值作为绑定值。 回顾之前的例子,可以显式指定linkState从 event.targetvalue 属性中获取新的状态值:

import linkState from 'linkstate';

class Foo extends Component {
  render({ }, { answer }) {
    return (
      <div>
        <input type="radio" name="demo"
          value="yes" checked={answer == 'yes'}
          onChange={linkState(this, 'answer', 'target.value')}
        />
        <input type="radio" name="demo"
          value="no" checked={answer == 'no'}
          onChange={linkState(this, 'answer', 'target.value')}
        />
      </div>
    );
  }
}

现在这个例子可以按照预期运行了!