我有一个旧版Backbone应用程序,我已经开始在React中重写它。该应用程序有一个主视图,其中包含两个子视图,这些子视图是按照排列顺序排列的。顶部面板显示一些数据,底部面板显示一些以该数据为输入的算法的结果。由于我有许多不同的数据源,每个数据源都应用了不同的算法,因此我有一个抽象的基类View,然后为每个数据源创建了子类,并根据需要添加,修饰和重写方法。像这样:
// Base View.
const BaseView = Backbone.View.extend({
events: {},
initialize() {
this.subViewA = // instantiate subview...
this.subViewB = // instantiate subview...
},
generateResultData() {
// 'Abstract' method which should be specialised to generate data rendered by subViewB...
},
render() {
// render subviews...
},
});
// Derived View.
const Derived = BaseView.extend({
events: {
// event handlers...
},
add(a, b) {
return a+b;
},
// additional methods...
generateResultData() {
return {
result: this.add(2,2);
}
},
})
这导致许多相似的View类的层次结构很浅。这都是非常必要的,但这是一个简单,直观且易于理解的模式,并且有效。但是,我正在努力寻找如何在React中实现相同的目标。假设React.Component
的子类的子类被视为反模式,我的重点自然是组合,尤其是高阶组件。 HOC(我发现它很漂亮,但不直观,并且常常令人困惑)似乎涉及添加一般功能,而不是专门化/完善更一般的内容。我还考虑过通过props传递更专业的Componenet方法版本。但这只是意味着我必须一遍又一遍地使用相同的样板组件定义:
// General functional component, renders the result of prop function 'foo'.
function GeneralComponent(props) {
const foo = this.props.foo || ()=>"foo";
return (
<div>
<span> { this.props.foo() } </span>
</div>
)
}
// Specialised component 1, overrides 'foo'.
class MySpecialisedComponent extends React.Component {
foo() {
return this.bar()
}
bar() {
return "bar"
}
render() {
return (
<GeneralComponent foo={this.foo} />
)
}
}
// Specialised component 2, overrides 'foo' and adds another method.
class MyOtherSpecialisedComponent extends React.Component {
foo() {
return this.bar() + this.bar()
}
bar() {
return "bar"
}
baz() {
return "baz"
}
render() {
return (
<GeneralComponent foo={this.foo} />
)
}
}
显然,以上是一个非常简单的情况,但是本质上捕获了我需要做的事情(尽管我当然会操纵状态,为简单起见,本示例不这样做)。我的意思是,我可以做这样的事情。但是我要避免不得不在整个地方重复该样板。那么,有没有更简单,更优雅的方法呢?
答案 0 :(得分:2)
通常,如果组件是无状态的并且不使用生命周期挂钩,则没有理由将其设为Component
类。充当名称空间且不持有状态的类可以被视为JavaScript中的反模式。
与其他框架相反,React没有模板,因此不需要映射变量以使其在视图中可用,因此唯一需要提及bar
函数的地方是被称为。 JSX是JavaScript的扩展,JSX表达式可以使用当前作用域中可用的任何名称。这样就可以在没有任何类的情况下编写函数:
const getBar => "bar";
const getBaz => "baz";
const getBarBaz => getBar() + getBaz();
const MySpecialisedComponent = props => <GeneralComponent foo={getBar} />;
const MyOtherSpecialisedComponent = props => <GeneralComponent foo={getBarBaz} />;
可以将匿名函数作为foo
道具来传递,而不是创建getBarBaz
,但是由于不必要的开销,通常不建议这样做。
此外,可以使用defaultProps
分配默认prop值,而无需在每个组件调用上创建新的()=>"foo"
函数:
function GeneralComponent({ foo }) {
return (
<div>
<span> {foo()} </span>
</div>
)
}
GeneralComponent.defaultProps = { foo: () => 'foo' };
答案 1 :(得分:1)
IMO让您失望的不是继承vs合成,而是您的数据流:
例如,我的许多派生视图都需要在主渲染之后进行自定义渲染。我正在使用第三方SVG库,渲染到“结果”子视图中的数据是通过分析其上方主数据视图中的渲染SVG元素得出的。
因此,您要在此处执行的操作是渲染后对远相关组件的子项更新道具,对吗?这样吗?
// after the svg renders, parse it to get data
<div id="svg-container">
<svg data="foo" />
<svg data="bar />
</div>
// show parsed data from svg after you put it through your algos
<div id="result-container">
// data...
</div>
有很多状态管理库可以帮助您解决此问题,即在一个组件中生成数据并将其广播到远距离相关的组件。如果您想使用内置的工具来解决此问题,则可以使用context
,它为您提供了一个全局存储,您可以将其提供给想要使用它的任何组件。
在您的示例中,子类具有特定于数据的方法(add
等)。 IMO在响应时更典型的做法是拥有一个通用类来显示数据,并简单地将其作为道具传递给map函数,以便重新排列/变换渲染的数据。
class AbstractDataMap extends PureComponent {
static defaultProps = {
data: [],
map: (obj, i) => (<div key={i}>{obj}</div>)
};
render() {
const { data, map, children } = this.props;
const mapped = data.map(map);
return (
<Fragment>
{mapped.map((obj, i) => (
children(obj, i)
))}
</Fragment>
);
}
}
// in some other container
class View extends Component {
render() {
return (
<div>
<AbstractDataMap data={[1, 2, 3]} map={(n) => ({ a: n, b: n + 1 })}>
{({ a, b }, i) => (<div key={i}>a: {a}, b: {b}</div>)}
</AbstractDataMap>
<AbstractDataMap data={[2, 4, 6]} map={(n) => (Math.pow(n, 2))}>
{(squared, i) => (<div key={i}>squared: {squared}</div>)}
</AbstractDataMap>
</div>
);
}
}
IMO,您正在寻找这种使用HOC来抽象化在渲染调用中明确使用.map
的工作的模式(以及其他用途)。但是,如上所述,HOC模式与您在同级组件之间共享数据存储的主要问题无关。
答案 2 :(得分:0)
回答我自己的问题,这是我从未见过的问题...
因此,我的问题确实来自一个担忧,即我需要重构大型,命令式和有状态的代码库,以便与React的基于组合的模型(以及Redux)集成。但是,在阅读了对我的问题的回答(非常有见地和很有帮助)后,我想到我的应用程序具有两个并行部分:UI和运行算法的引擎(实际上是音乐分析引擎)。而且我可以很容易地删除引擎连接到的Backbone View层。因此,使用React的context API,我构建了一个“ AnalysisEngineProvider”,该引擎可用于子组件。该引擎非常有必要,并且经典地是面向对象的,并且仍然使用Backbone模型,但这对UI没有影响,因为UI不了解其内部-这应该是这样(模型可能会被重构出来)也在某个时候)...
引擎还负责渲染SVG(不适用于BB视图)。但是React对此一无所知。它只是看到一个空的div。我从div中选取了ref并将其传递给引擎,以便引擎知道在哪里渲染。除了引擎和UI几乎没有联系之外-div根本不会从React状态更改中进行更新(显然,UI的其他组件也是如此)。引擎中的模型只会触发SVG的更新,而React对此一无所知。
至少到现在,我对这种方法感到满意-即使这只是对完全React解决方案的增量重构的一部分。无论我碰巧正在使用什么框架,这都感觉像是针对该应用程序的正确设计。