早在2017年,Wayfair作出通过决定应对因为我们选择的主要前端框架。而不是套我们为什么选择阵营的原因,更有趣的是,我们希望把重点放在一些架构的挑战,我们在转换过程中面临的。

当项目在2017年6月正式启动,我们必须用很少的经验做出反应超过500软件工程师。最重要的是,我们吹嘘建在1500多个骨干机型我们VDOM渲染架构,和几十个新功能,以完成紧张的时间,数以百计的计划或在飞行中A / B特性测试。我们的JavaScript测试覆盖率也很低,徘徊在2%左右。

正如一位工程师所言:“这就像在飞机飞行时更换飞机的每一个部件,只不过实际上有两架飞机,你不知道哪一架飞机用了哪个部件。”

尽管如此,我们还是决心让它发挥作用。在Wayfair上,我们从不认为自己已经完成了,这种转换被认为是实现可重用可视化组件的关键一步,也是前端架构的自然演进。

钨反应

我们有一个庞大的代码库与几个高度交互式组件,所有必须为客户提供最佳的购物体验。鉴于规模和我们的代码库的复杂性,就不可能转换整个页面反应。我们需要对钨部件的方式和反应的组分,以很好地一起玩,这样一个页面的部分可以被转换和部署。

我们的转换计划上看到我们开始组件树的叶子节点和递归转换了树。当所有的孩子们成功地转换为反应,我们会再转换父。后来更多,但首先,让我们来了解一些细节。

钨成分有“暴露的功能”和“暴露事件”而形成的一种API的,或合同,组件之间。任何暴露的事件将从组件到母体,它可以访问直接调用关于儿童任何暴露的功能冒泡。通过从父母传下来的孩子反应道具相通。我们需要的是一种方式嵌入在钨成分反应成分。

//特殊函数传递模型暴露的事件和函数//通过包装组件函数getBoundReactComponent(WrappedComponent, tungstenModel)const exposedFunctions = getExposedFunctions(WrappedComponent, tungstenModel);函数BoundReactComponent(props) {return ;}返回{BoundReactComponent,};}

这个高阶组分(HOC)函数的暴露的功能和暴露事件关闭钨模型实例,并将它们作为道具根阵营组件。

常量EL = opts.el常量doRender =函数(){常量modelData = tungstenModelInstance.toJSON();ReactDOM.render(报);};doRender();tungstenModelInstance.on( '变更呈现',doRender);

当构造钨成分,我们简单地呈现在所述钨根阵营根及导线上重新呈现任何时间钨包装变化或重新渲染。

破坏(){model.off( '变​​更呈现',doRender);ReactDOM.unmountComponentAtNode(EL);},

最后,钨组件有一个销毁功能,我们连接它来卸载根React组件和解除事件处理程序的绑定。这段代码允许我们切断DOM树的分支,让它们完全反应:

“/

但是让里面的钨元素发生反应呢?很多工程师都想要这个功能,我们也认真考虑过。这当然是可行的:只要从“componentShouldUpdate”返回false,这样就有了一个DOM分支,可以让钨接管(或者JQuery,或者任何其他你想要的库)。这对于DOM的隐蔽分支来说很好,但是Wayfair组件是高度交互的。钨->反应->钨之间的通信将变得非常难以管理。另外,这两个框架之间的基本架构模式也不能很好地混合。

如果我们想数百名工程师开始“在思维做出反应”,我们需要划清界限。

对于如此大的反应的代码库,数据管理是必然上来。详尽的调查后,值得它自己的博客文章中,我们决定去与终极版。这向我们提出了一个新的问题:我们如何让不同的反应,和/终极版“应用程序”共享存储和接收通量架构的所有真棒好处?

//全局提供程序操作类型const ADD_ROOT_KEY = '@@global/provider/ ADD_ROOT_KEY ';//将数据添加到全局存储操作创建器const globalAddData = payload => ({type: ADD_ROOT_KEY, payload});const globalProviderReducer = (state ={},{类型,有效负载})=> {switch (type) {case ADD_ROOT_KEY: return{…状态,……载荷};默认值:返回状态;}};让globalStore = createStore(() =>{},增强器);让globalReducers = {};// const composeNewReducer = reducers => (state = {}, action) =>组合还原器(reducers)(globalProviderReducer(state, action), action);类GlobalProvider扩展Component {componentWillMount() {const state = globalStore.getState();这一点。register( this.props.reducers, resolveData(state, this.props.initialData), state ); } register(newReducer, newData, oldData) { globalReducers = { ...globalReducers, ...newReducer }; globalStore.replaceReducer(composeNewReducer(globalReducers)); globalStore.dispatch(globalAddData(newData)); } render() { return {this.props.children}; } }

这是我们的全球供应商的简化版本,它的工作方式类似于终极版`Provider`但不是通过一家新店,它钩住你到全局存储。这使我们能够重用Redux的选择跨单独的“应用程序”,因为我们转换了树。

我们的内部反应组件库也发挥了主要作用的速度转换。在仅仅6个月的时间里,我们100%的核心移动路径和将近90%的移动页面都被正式转换为响应。反思这个庞大的项目,让我们探讨一些明显的成功因素和经验教训。

成功因素

  1. 反应!学习React是一件很棒的事情,因为它使得代码编写变得非常容易,一些团队不仅能够转换,而且还能够利用这个机会来清理他们的代码债。
  2. 全球的支持!产品经理和整个业务部门都完全理解重构的需要,并允许我们有时间工作,即使它对网站没有明显的影响。让所有团队(包括业务团队)在同一页面上进行此工作,可以使所有必需的部分都到位。
  3. 开拓者!JavaScript的平台团队在这里Wayfair,再加上一对夫妇尝鲜的,清除了不少路障。当任何问题来了,有人总是在那里帮助。

教训和斗争

  1. 跨团队阻滞剂绝对明显。协调如此大规模的,相互依存的项目势必会有一些意想不到的时机问题,特别是考虑到我们工程部的大小。几支球队分别由依赖于一个需要被转换其他球队的必要模式库组件的功能被阻塞。
  2. 钨和反应相互作用,但粗糙。系统在一个体系结构中运行得越快,对系统进行任何必要的更新就越容易。

结论

总体而言,这个项目是Wayfair了巨大的成功。采用迅速作出反应,打开门我们雇用的管道,使我们能够招募优秀的工程师。它还通过缩短时间,测试和部署新的功能,前进带来的凝聚力和专业知识,我们的代码库。我们很高兴已经能够拉过这样的壮举,和上面列出的见解是非常有价值的。

你呢?做自己喜欢的事情使用等反应,终极版和GraphQL解决复杂的问题?看看我们工程中的开放角色今天!