在 KnockoutJS 中使用 React 组件

Using React components inside KnockoutJS

我们有一个主要使用 KnockoutJS 构建的大型 Web 应用程序。我正在研究是否有可能迁移到 React,但是需要重写整个应用程序。我的想法是使用自下而上的方法:从一个一个地替换基本构建块开始。

我受到一些 prior work 的启发,在 KnockoutJS 绑定处理程序中调用 ReactDOM.render:

ko.bindingHandlers.react = {
    init() {
        return {controlsDescendantBindings: true};
    },
    update(element, valueAccessor) {
        const {component, props} = valueAccessor();
        ReactDOM.render(React.createElement(component, props), element);
    }
};

Knockout 有自己的依赖跟踪系统,所以它会在某些数据发生变化时调用更新方法。

它运行完美,并且重新呈现组件以反映对数据的任何更改。但是,当在 React 事件处理程序中更新数据时,它会崩溃。例如,此组件未按预期工作:

const Input = function ({value}) {
    return <input type="text" 
                  value={value()} 
                  onChange={e => value(e.target.value)}/>;
}

注意:本例中的值是 ko.observable,像在事件处理程序中那样调用它会导致调用 bindingHandler 的更新方法,进而调用 ReactDOM.render。但是,此渲染仅工作一次,之后组件停止更新。

The issue is demonstrated in this CodePen。单击该框并尝试输入内容。一次更新完成后,组件函数停止调用

编辑:我认为问题是对 ReactDOM.render 的第二次调用(当用户在 onChange 处理程序中更新值时)不是同步的。这意味着 Knockout 的依赖检测无法工作,任何后续调用都不再调用绑定处理程序的更新方法。

这可以通过某种方式规避吗?

为我工作:

 update(element, valueAccessor) {
    const {component, props} = valueAccessor();
    props.value(props.value());
    ReactDOM.render(React.createElement(component, props), element);
}

如果你刷新你的 observable 它会起作用,但不幸的是我不知道如何。

如我所料,问题似乎是 ReactDOM.render 在某些情况下是异步的 - 在这种情况下,当从 React 中的事件处理程序调用时。

这意味着如果您在更新方法本身中取消引用您依赖的任何可观察对象,Knockout 的依赖项跟踪机制将按预期工作。这就是为什么 Gawel1908 提出的修改使其起作用的原因 - 不是因为值是 "reset",而是因为 props.value 被取消引用。

我决定改用一个约定:始终在 valueAccessor 本身中解包任何此类可观察对象:

<div data-bind="react: { component: Input, props: { value: val(), setValue: val }}">
</div>

并且不要在组件中解包它:

const Input = function ({value, setValue}) {
  return <input 
           type="text" 
           value={value} 
           onChange={e => setValue(e.target.value)}/>;
}

Updated, working codepen.